//#define TWOSTAGE // this directive forces the compiler to use only two stages for extended shader conversion

using System;
using System.IO;
using System.Drawing;
using System.Collections;
using System.Windows.Forms;

namespace Magic.Core
{
    /// <summary>
    /// An object representation of a Halo map file.
    /// </summary>
    public class HaloMap : IDisposable
    {
        #region Structures
        #region Metadata
        private struct PointerList
        {
            public int Count;
            public ArrayList Locations;
            public ArrayList Destinations;

            public PointerList(ArrayList locs, ArrayList dests)
            {
                int lCount = locs.Count;
                int dCount = dests.Count;
                if (lCount != dCount)
                    throw new Exception("PointerList location count and destination count did not match.");
                Count = lCount;
                Locations = locs;
                Destinations = dests;
            }
        }
        #endregion
        #region Constants
        public enum MapFormat : int
        {
            Xbox = 5,
            PC = 7,
            CE = 609,
            Undefined = -1
        }
        public enum MapUsage : int
        {
            Campaign = 0,
            Multiplayer = 1,
            UI = 2
        }
        public enum HeaderLengths : short
        {
            Xbox = 36,
            PC = 40,
            CE = 40
        }
        public enum BspMagic : uint
        {
            Xbox = 0x819a6000
        }
        public enum IndexMagic : uint
        {
            Xbox = 0x803a6024,
            CE = 0x40440028
        }
        public class MapBuildVersion
        {
            public const string NTSCXbox = "01.10.12.2276";
            public const string PALXbox = "01.01.14.2342";
            public const string PC = "01.00.00.0564";
            public const string CE = "01.00.00.0609";
        }
        #endregion
        #endregion
        #region Declarations
        private uint magic;
        private int count;
        private uint indexMagic;
        private int indexOffset;
        private int modelIndexCount;
        private uint modelIndexOffset;
        private int modelVertexCount;
        private uint modelVertexOffset;
        private uint scenarioIdentifier;
        private MapFormat format;
        public MapUsage usage;
        private short headerSize;
        private string name;
        private string filename;
        private string tagDirectory;
        private IndexItem[] index;
        private EndianReader er;
        private EndianWriter ew;
        private EndianReader bitmaps;
        private EndianReader sounds;
        private TagLibrary tags;
        private Cxpe structs;
        private ToolStripStatusLabel sLabel;
        #endregion

        #region Loading
        private void LoadScenario(string ceTagsDirectory)
        {
            Status("Compiling tag references...");
            name = Path.GetFileNameWithoutExtension(filename);
            tags = new TagLibrary(structs);
            tags.StatusMessage += new TagLibrary.MessageEventHandler(Status);
            string process = filename.Substring(tagDirectory.Length);
            Tag tagScenario = tags[tags.GetDependencyTag(process.Substring(0, process.LastIndexOf('.')), "scnr", tagDirectory, ceTagsDirectory)];

            Tag tagGlobals = null;

            short mapUsage = (short)tagScenario["type"];
            switch ((short)tagScenario["type"])
            {
                case 0:
                    usage = MapUsage.Campaign;
                    process = @"ui\shell\solo";
                    tagGlobals = tags[tags.GetDependencyTag("globals\\campaign", "matg", tagDirectory, "globals\\globals", false, ceTagsDirectory)];
                    break;
                case 1:
                    usage = MapUsage.Multiplayer;
                    process = @"ui\shell\multiplayer";
                    tagGlobals = tags[tags.GetDependencyTag("globals\\multiplayer", "matg", tagDirectory, "globals\\globals", false, ceTagsDirectory)];
                    break;
                case 2:
                    usage = MapUsage.UI;
                    process = @"ui\shell\main_menu";
                    tagGlobals = tags[tags.GetDependencyTag("globals\\main_menu", "matg", tagDirectory, "globals\\globals", false, ceTagsDirectory)];
                    break;
                default:
                    throw new Exception("The scenario map type flag is invalid.");
            }

            Tag tagMultiplayerText = tags[tags.GetDependencyTag("ui\\multiplayer_game_text", "ustr", tagDirectory, ceTagsDirectory)];
            Tag tagDefaultWhite = tags[tags.GetDependencyTag(@"ui\shell\bitmaps\white", "bitm", tagDirectory, ceTagsDirectory)];
            Tag tagUserInterface = tags[tags.GetDependencyTag(process, "Soul", tagDirectory, ceTagsDirectory)];

            if (usage == MapUsage.UI)
            {
                Tag tagTempTag;
                tagTempTag = null; tagTempTag = tags[tags.GetDependencyTag("ui\\english", "vcky", tagDirectory, ceTagsDirectory)];
                tagTempTag = null; tagTempTag = tags[tags.GetDependencyTag("ui\\random_player_names", "ustr", tagDirectory, ceTagsDirectory)];
                tagTempTag = null; tagTempTag = tags[tags.GetDependencyTag("ui\\multiplayer_scenarios", "mply", tagDirectory, ceTagsDirectory)];
                tagTempTag = null; tagTempTag = tags[tags.GetDependencyTag("ui\\saved_game_file_strings", "ustr", tagDirectory, ceTagsDirectory)];
                tagTempTag = null; tagTempTag = tags[tags.GetDependencyTag("ui\\default_multiplayer_game_setting_names", "ustr", tagDirectory, ceTagsDirectory)];
                tagTempTag = null; tagTempTag = tags[tags.GetDependencyTag("ui\\shell\\strings\\game_variant_descriptions", "ustr", tagDirectory, ceTagsDirectory)];
                tagTempTag = null; tagTempTag = tags[tags.GetDependencyTag("ui\\shell\\strings\\default_player_profile_names", "ustr", tagDirectory, ceTagsDirectory)];
                tagTempTag = null; tagTempTag = tags[tags.GetDependencyTag("ui\\shell\\main_menu\\player_profiles_select\\button_set_long_descriptions", "ustr", tagDirectory, ceTagsDirectory)];
                tagTempTag = null; tagTempTag = tags[tags.GetDependencyTag("ui\\shell\\main_menu\\player_profiles_select\\button_set_short_descriptions", "ustr", tagDirectory, ceTagsDirectory)];
                tagTempTag = null; tagTempTag = tags[tags.GetDependencyTag("ui\\shell\\main_menu\\player_profiles_select\\joystick_set_short_descriptions", "ustr", tagDirectory, ceTagsDirectory)];
                tagTempTag = null; tagTempTag = tags[tags.GetDependencyTag("sound\\sfx\\ui\\cursor", "snd!", tagDirectory, ceTagsDirectory)];
                tagTempTag = null; tagTempTag = tags[tags.GetDependencyTag("sound\\sfx\\ui\\flag_failure", "snd!", tagDirectory, ceTagsDirectory)];
                tagTempTag = null; tagTempTag = tags[tags.GetDependencyTag("sound\\music\\title1\\title1", "lsnd", tagDirectory, ceTagsDirectory)];
                tagTempTag = null;
            }

            string[] sbsps = tags.StructureBsps;
            Array scenarioBspBlock = tagScenario["structure bsps"] as Array;
            for (int x = 0; x < sbsps.Length; x++)
            {
                int bspTagIndex = tags.GetDependencyTag(sbsps[x], "sbsp", tagDirectory, sbsps[x], true, ceTagsDirectory);
                scenarioBspBlock.SetValue(bspTagIndex, x, 4 /* this is the bsp tag reference */);
            }
            Status("Verifying that all tags exist...");
            tags.AssertAllTagsPresent();
            Status("Finished.");
        }
        private void LoadMap()
        {
            name = Path.GetFileNameWithoutExtension(filename);
            int bmiCount = 0;
            int[] ceBitmapOffsets = null;
            switch (format)
            {
                case MapFormat.Xbox:
                    headerSize = (short)HeaderLengths.Xbox;
                    break;
                case MapFormat.PC:
                    headerSize = (short)HeaderLengths.PC;
                    bitmaps.Position = 8;
                    int bmiOffset2 = bitmaps.ReadInt32();
                    bmiCount = bitmaps.ReadInt32();
                    ceBitmapOffsets = new int[bmiCount];
                    for (int bmi = 0; bmi < bmiCount; bmi++)
                    {
                        bitmaps.Position = 12 * bmi + bmiOffset2 + 8;
                        ceBitmapOffsets[bmi] = bitmaps.ReadInt32();
                    }
                    break;
                case MapFormat.CE:
                    headerSize = (short)HeaderLengths.CE;
                    bitmaps.Position = 8;
                    int bmiOffset = bitmaps.ReadInt32();
                    bmiCount = bitmaps.ReadInt32();
                    ceBitmapOffsets = new int[bmiCount];
                    for (int bmi = 0; bmi < bmiCount; bmi++)
                    {
                        bitmaps.Position = 12 * bmi + bmiOffset + 8;
                        ceBitmapOffsets[bmi] = bitmaps.ReadInt32();
                    }
                    break;
                default:
                    throw new Exception("Invalid map file.");
            }
            er.Position = 0x08;
            int size = er.ReadInt32();
            if (size > er.BaseStream.Length)
            {
                GC.Collect();
                Status("Decompressing map file...");
                er.Position = 0x800;

                FileStream fStream = new FileStream(Path.GetTempFileName(), FileMode.Create);

                byte[] compressed = er.ReadBytes((int)er.BaseStream.Length - 0x800);
                MemoryStream mStream = new MemoryStream(compressed);

                ICSharpCode.SharpZipLib.Zip.Compression.Streams.InflaterInputStream decompressed = new ICSharpCode.SharpZipLib.Zip.Compression.Streams.InflaterInputStream(mStream);
                //decompressed.Write(er.ReadBytes((int)er.BaseStream.Length - 0x800), 0, (int)er.BaseStream.Length - 0x800);
                //decompressed.

                er.Position = 0;
                fStream.Write(er.ReadBytes(0x800), 0, 0x800);

                byte[] buff = new byte[0x1000];
                int bytesRead = 0;
                do
                {
                    bytesRead = decompressed.Read(buff, 0, buff.Length);
                    fStream.Write(buff, 0, bytesRead);
                    Application.DoEvents();
                } while (bytesRead > 0);

                mStream.Close();
                compressed = null;
                er = new EndianReader(fStream, ByteOrder.LittleEndian);
            }
            er.Position = 0x10;
            indexOffset = er.ReadInt32();
            er.Position = 0x60;
            usage = (MapUsage)er.ReadInt16();
            er.Position = indexOffset;
            indexMagic = er.ReadUInt32();
            scenarioIdentifier = er.ReadUInt32();
            er.Position += 4;
            count = er.ReadInt32();
            modelVertexCount = er.ReadInt32();
            modelVertexOffset = er.ReadUInt32();
            modelIndexCount = er.ReadInt32();
            modelIndexOffset = er.ReadUInt32();
            magic = (uint)(indexMagic - headerSize - indexOffset);
            tags = new TagLibrary(structs);
            tags.Bitmaps = bitmaps;
            tags.Sounds = sounds;
            tags.MapFormat = format;
            tags.ModelIndexOffset = (int)modelIndexOffset;
            tags.ModelVertexOffset = (int)modelVertexOffset;

            er.Position = indexOffset + headerSize;
            index = new IndexItem[count];
            string[] names = new string[count];
            Status("Reading object index...");
            for (int x = 0; x < count; x++)
            {
                index[x] = new IndexItem();
                index[x].Read(er, magic);
                if (index[x].type == "prot")
                {
                    index[x].type = "scnr";
                }
                if (index[x].type == "devm")
                {
                    index[x].type = "scnr";
                }
            }

            for (int x = 0; x < count; x++)
            {
                er.Position = (int)index[x].stringOffset;
                if (index[x].type == "ztpt")
                {
                    names[x] = "<protected>";
                    index[x].type = index[x].parent;
                    if (index[x].type == index[x].grandparent)
                    {
                        Console.WriteLine(x.ToString());
                        //Console.WriteLine(index[x+1].type);
                        index[x].type = "obje";
                    }
                }
                else
                    names[x] = er.ReadString().Trim((char)0);
                if (names[x] == "<protected>" || names[x] == "<zteam>" || names[x].Contains("@"))
                {
                    names[x] = "levels\\test\\" + name + "\\protected\\" + name + "_" + x.ToString();
                    if (index[x].type == "scnr" | index[x].type == "sbsp")
                    { names[x] = "levels\\test\\" + name + "\\" + name; }

                    index[x].isprotected = true;
                }
                tags.AddTag(names[x], index[x].type, index[x].identifier, null);
            }
            Tag bspTag = null;
            int bspIndex = 0;
            for (int x = 0; x < count; x++)
            {
                Status("Loading tag '" + names[x].TrimEnd(new char[] { '\0' }) + '.' + structs[index[x].type].Extension + '\'');
                if (format == MapFormat.CE)
                {
                    if ((index[x].flags & 1) > 0)
                    {
                        Console.WriteLine("Loading tag '" + names[x].TrimEnd(new char[] { '\0' }) + '.' + structs[index[x].type].Extension + '\'');
                        if (index[x].type == "bitm")
                        {
                            if (index[x].metaOffset < bmiCount)
                                bitmaps.Position = ceBitmapOffsets[index[x].metaOffset];
                            else
                                continue;
                            tags[x] = new Tag(bitmaps, names[x], "bitm", ToUnsignedByReference(-bitmaps.Position), tags);
                            continue;
                        }
                        if (index[x].type == "snd!")
                            continue;
                        if (index[x].type == "ustr")
                            continue;
                        if (index[x].type == "font")
                            continue;
                        if (index[x].type == "hmt ")
                            continue;
                    }
                }
                Tag tag = null;
                uint bspMagic = 0;
                int bspOffset = 0;
                if (index[x].type == "sbsp")
                {
                    Array scenarioStructureBsps = tags[0]["structure bsps"] as Array;
                    er.Position = bspOffset = (int)(scenarioStructureBsps.GetValue(bspIndex, 0 /* this is bsp offset */));
                    bspMagic = (uint)(int)(scenarioStructureBsps.GetValue(bspIndex++, 2 /* this is bsp magic */));
                    er.Position = (int)(er.ReadUInt32() - bspMagic) + bspOffset;
                }
                else
                    er.Position = (int)index[x].metaOffset;
                if (index[x].type == "sbsp")
                {
                    tag = new Tag(er, names[x], index[x].type, (uint)(bspMagic - bspOffset), tags);
                    tag.isprotected = index[x].isprotected;
                    bspTag = tag;

                    Xpe.Field cLightmap = tag.Xpe.FieldByName("structure lightmaps");
                    Array bspLightmaps = tag["structure lightmaps"] as Array;
                    int lmCount = bspLightmaps.GetLength(0);
                    for (int y = 0; y < lmCount; y++)
                    {
                        Array bspMaterials = bspLightmaps.GetValue(y, cLightmap["structure materials"]) as Array;
                        int matCount = bspMaterials.GetLength(0);
                        Xpe.Field cMaterial = cLightmap.FieldByName("structure materials");
                        bool noLightmap = (short)bspLightmaps.GetValue(y, cLightmap["bitmap"]) < 0;

                        for (int z = 0; z < matCount; z++)
                        {
                            Tag.DataContainer cvdc = bspMaterials.GetValue(z, cMaterial["compressed vertices"]) as Tag.DataContainer;
                            Tag.DataContainer uvdc = bspMaterials.GetValue(z, cMaterial["uncompressed vertices"]) as Tag.DataContainer;
                            byte[] bspUncompressedVertices = uvdc.data;
                            if (bspUncompressedVertices.GetLength(0) == 0)
                            {
                                bspUncompressedVertices = HaloMap.DecompressBSPVertices(cvdc.data, !noLightmap);
                                uvdc.data = bspUncompressedVertices;
                                uvdc.variable = cvdc.variable;
                                cvdc.data = new byte[0];
                                cvdc.variable = 0;
                            }

                        }
                    }

                }
                else
                {
                    tag = new Tag(er, names[x], index[x].type, magic, tags);
                    tag.isprotected = index[x].isprotected;
                }
                tags[x] = tag;
            }
            if (bspTag.isprotected)
            {
                int lTag = (int)bspTag["lightmaps"];
                tags.Refactor(lTag, "levels\\test\\" + name + "\\" + name);
            }
            Status("Applying semantic correction...");
            string baseDirectory = names[0].Substring(0, names[0].LastIndexOf('\\') + 1);
            for (int x = 0; x < count; x++)
            {
                if (names[x].IndexOf('\\') < 0)
                {
                    names[x] = baseDirectory + names[x];
                    tags.Refactor(x, names[x]);
                }
            }

            Status("Deprotecting Map.");

            int tCount = 0;
            int pCount = 0;
            for (int x = 0; x < this.Library.Count; x++)
            {
                Tag nTag = this.Library[x];
                if (nTag != null)
                {

                    if (nTag.isprotected == true)
                        pCount++;
                    Library.CXPE.RootTag = x;
                    int tagIndex = nTag.CheckHash(this.Library);
                    if (tagIndex != -1)
                    {
                        this.Library[x].isprotected = false;
                        this.Library.Refactor(x, this.Library.CXPE.md5Name[tagIndex].ToString());
                        tCount++;
                    }
                    else
                    {

                    }
                }
            }

            Status("Finished.");
        }

        public HaloMap(string file, string ceTagsDirectory, string ceBitmaps, string ceSounds, string tagDirectoryName, Cxpe structureList, ToolStripStatusLabel status)
        {
            sLabel = status;
            structs = structureList;
            filename = file;
            tagDirectory = tagDirectoryName;
            Status("Preparing to load file...");
            string fileExtension = Path.GetExtension(file).Remove(0, 1);
            if (fileExtension == "map")
            {
                er = new EndianReader(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), ByteOrder.LittleEndian);
                er.Position = 0x4;
                format = (MapFormat)er.ReadInt32();
                switch (format)
                {
                    case MapFormat.CE:
                    case MapFormat.PC:
                        bitmaps = new EndianReader(new FileStream(ceBitmaps, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), ByteOrder.LittleEndian);
                        sounds = new EndianReader(new FileStream(ceSounds, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), ByteOrder.LittleEndian);
                        break;
                    default:
                        bitmaps = null;
                        sounds = null;
                        break;
                }
                LoadMap();
            }
            else if (fileExtension == "scenario")
                LoadScenario(ceTagsDirectory);
            else
                throw new Exception("Invalid file to load as a map object.");
            GC.Collect();
        }


        #endregion
        #region Saving
        public void SaveAsXbox(string fn, bool pal, string internalname)
        {
            SaveAsXbox(fn, pal, internalname, false);
        }
        public void SaveAsXbox(string fn, bool pal, string internalname, bool compress)
        {
            #region Attach streams to buffers and create a 'predictor'
            Status("Preparing to compile...");
            count = tags.Count;
            EndianWriter header = new EndianWriter(new MemoryStream(), ByteOrder.LittleEndian); // holds the map header, dumbass
            EndianWriter bspBuffer = new EndianWriter(new MemoryStream(), ByteOrder.LittleEndian); // holds bsp header, vertices, triangles, and meta
            EndianWriter indexBuffer = new EndianWriter(new MemoryStream(), ByteOrder.LittleEndian); // holds the map object index and filenames
            EndianWriter modelIndirectVertexBuffer = new EndianWriter(new MemoryStream(), ByteOrder.LittleEndian); // holds model vertex pointers
            EndianWriter modelVertexBuffer = new EndianWriter(new MemoryStream(), ByteOrder.LittleEndian); // holds model vertices
            EndianWriter modelIndirectIndexBuffer = new EndianWriter(new MemoryStream(), ByteOrder.LittleEndian); // holds model index pointers
            EndianWriter modelIndexBuffer = new EndianWriter(new MemoryStream(), ByteOrder.LittleEndian); // holds model indices
            EndianWriter metaBuffer = new EndianWriter(new MemoryStream(), ByteOrder.LittleEndian); // holds tag metadata
            EndianWriter local = new EndianWriter(new FileStream(fn, FileMode.Create, FileAccess.ReadWrite, FileShare.None), ByteOrder.LittleEndian); // Map is outputted through this stream; also holds textures and sounds at compiletime
            Predictor predictor = new Predictor(tags);
            #endregion
            #region Prepare the model vertex and index buffers, cull uncompressed model vertices
            Status("Streaming model vertex and index buffers; culling uncompressed model vertices...");
            ArrayList indirectVertexOffsets = new ArrayList();
            ArrayList indirectIndexOffsets = new ArrayList();
            ArrayList vertexOffsets = new ArrayList();
            ArrayList indexOffsets = new ArrayList();
            ArrayList vertexCounts = new ArrayList();
            ArrayList indexCounts = new ArrayList();
            for (int x = 0; x < count; x++)
            {
                Xpe mode = structs["mode"];
                Xpe mod2 = structs["mod2"];
                Xpe xModel = null;
                if (tags.GetClass(x) == "mod2" || tags.GetClass(x) == "mode")
                {
                    if (tags.GetClass(x) == "mod2")
                        xModel = mod2;
                    else
                        xModel = mode;

                    Array geometry = tags[x]["geometries"] as Array;
                    int geometries = geometry.GetLength(0);
                    for (int y = 0; y < geometries; y++)
                    {
                        Array part = geometry.GetValue(y, xModel.FieldByName("geometries")["parts"]) as Array;
                        int parts = part.GetLength(0);
                        for (int z = 0; z < parts; z++)
                        {
                            // These will speed this method up by quite a bit, and make it much more readable
                            Xpe.Field cPart = xModel.FieldByName("geometries").FieldByName("parts");
                            Xpe.Field cVertex = cPart.FieldByName("compressed vertices");
                            Xpe.Field cFace = cPart.FieldByName("triangles");

                            // Write the compressed vertices to the vertex buffer, or if they do not exist, compress the uncompressed vertices and use them instead
                            Array vertex = part.GetValue(z, cPart["compressed vertices"]) as Array;
                            bool uncompressed = false;
                            int verts = vertex.GetLength(0);
                            vertexCounts.Add(verts);
                            if (uncompressed = (verts == 0))
                            {
                                cVertex = cPart.FieldByName("uncompressed vertices");
                                vertex = part.GetValue(z, cPart["uncompressed vertices"]) as Array;
                                verts = vertex.GetLength(0);
                            }
                            modelVertexBuffer.Position = modelVertexBuffer.Length = Round(modelVertexBuffer.Length, 32);
                            vertexOffsets.Add((uint)modelVertexBuffer.Length);
                            for (int i = 0; i < verts; i++)
                            {
                                modelVertexBuffer.Write((float)(vertex.GetValue(i, cVertex["position"]) as Array).GetValue(0));
                                modelVertexBuffer.Write((float)(vertex.GetValue(i, cVertex["position"]) as Array).GetValue(1));
                                modelVertexBuffer.Write((float)(vertex.GetValue(i, cVertex["position"]) as Array).GetValue(2));
                                if (uncompressed)
                                {
                                    modelVertexBuffer.Write(Compress111110(vertex.GetValue(i, cVertex["normal"]) as Array));
                                    modelVertexBuffer.Write(Compress111110(vertex.GetValue(i, cVertex["binormal"]) as Array));
                                    modelVertexBuffer.Write(Compress111110(vertex.GetValue(i, cVertex["tangent"]) as Array));
                                    modelVertexBuffer.Write(Compress1616(vertex.GetValue(i, cVertex["texture coords"]) as Array));
                                    modelVertexBuffer.Write((byte)((short)vertex.GetValue(i, cVertex["node0 index"]) * 3));
                                    modelVertexBuffer.Write((byte)((short)vertex.GetValue(i, cVertex["node1 index"]) * 3));
                                    modelVertexBuffer.Write(CompressFraction16((float)vertex.GetValue(i, cVertex["node0 weight"])));
                                }
                                else
                                {
                                    modelVertexBuffer.Write((int)(vertex.GetValue(i, cVertex["normal[11.11.10-bit]"])));
                                    modelVertexBuffer.Write((int)(vertex.GetValue(i, cVertex["binormal[11.11.10-bit]"])));
                                    modelVertexBuffer.Write((int)(vertex.GetValue(i, cVertex["tangent[11.11.10-bit]"])));
                                    modelVertexBuffer.Write((short)(vertex.GetValue(i, cVertex["texture coordinate u[16-bit]"])));
                                    modelVertexBuffer.Write((short)(vertex.GetValue(i, cVertex["texture coordinate v[16-bit]"])));
                                    modelVertexBuffer.Write((byte)(vertex.GetValue(i, cVertex["node0 index(x3)"])));
                                    modelVertexBuffer.Write((byte)(vertex.GetValue(i, cVertex["node1 index(x3)"])));
                                    modelVertexBuffer.Write((short)(vertex.GetValue(i, cVertex["node0 weight[16-bit]"])));
                                }
                            }

                            // Write indices to index buffer
                            Array triangle = part.GetValue(z, cPart["triangles"]) as Array;
                            int triangles = triangle.GetLength(0);
                            modelIndexBuffer.Position = modelIndexBuffer.Length = Round(modelIndexBuffer.Length, 16);
                            indexOffsets.Add((uint)modelIndexBuffer.Length);
                            short difference = 2;
                            for (int i = 0; i < triangles; i++)
                            {
                                short a = (short)triangle.GetValue(i, cFace["vertex0 index"]);
                                short b = (short)triangle.GetValue(i, cFace["vertex1 index"]);
                                short c = (short)triangle.GetValue(i, cFace["vertex2 index"]);
                                if (i == triangles - 1)
                                {
                                    if (a == -1)
                                        difference += 3;
                                    else if (b == -1)
                                        difference += 2;
                                    else if (c == -1)
                                        difference += 1;
                                }
                                modelIndexBuffer.Write(a);
                                modelIndexBuffer.Write(b);
                                modelIndexBuffer.Write(c);
                            }
                            indexCounts.Add((int)(triangles * 3 - difference));

                            // Cull these structures from the tag
                            // TODO: create better way to cull model data that doesn't permanently scar the tags
                            //part.SetValue(Array.CreateInstance(typeof(object), 0, cPart.FieldByName("uncompressed vertices").Children.Length), z, cPart["uncompressed vertices"]);
                            //part.SetValue(Array.CreateInstance(typeof(object), 0, cPart.FieldByName("compressed vertices").Children.Length), z, cPart["compressed vertices"]);
                            //part.SetValue(Array.CreateInstance(typeof(object), 0, cFace.Children.Length), z, cPart["triangles"]);
                        }
                    }
                }

                // This 'converts' all pc 'mod2' tags into xbox 'mode' tags
                // TODO: Change this because it scars the structure file!! This only works if you close the application between builds!
                Xpe.Field geometryEdit = mod2.FieldByName("geometries");
                Xpe.Field geometryParts = geometryEdit.FieldByName("parts");

                geometryParts.Size = 104;
                geometryParts.Children[15].Size = 0;
            }
            #endregion
            #region Prepare the index
            Status("Writing an index shell...");
            indexBuffer.Write((uint)IndexMagic.Xbox);
            indexBuffer.Write(tags.GetID(0));
            indexBuffer.Write(0);
            indexBuffer.Write(count);
            indexBuffer.Position = 32;
            indexBuffer.Write(0x74616773); // 'tags'
            indexBuffer.Position = indexBuffer.Length += count * 32;
            uint[] stringOffsets = new uint[count];
            for (int x = 0; x < count; x++)
            {
                stringOffsets[x] = (uint)indexBuffer.Position;
                indexBuffer.Write(tags.GetName(x), true);
            }
            indexBuffer.Position = indexBuffer.Length = Round(indexBuffer.Length, 32);
            modelIndexBuffer.Position = modelIndexBuffer.Length = Round(modelIndexBuffer.Length, 16);
            modelVertexBuffer.Position = modelVertexBuffer.Length = Round(modelVertexBuffer.Length, 32);
            #endregion
            #region Calculate "magic" offsets
            // 'memory' is a handy little value that we can add to any post-model offset to simulate "magic", no matter what we do anywhere else now
            uint mIndexMagic = (uint)IndexMagic.Xbox;
            int mHeaderLength = (int)HeaderLengths.Xbox;
            int vertexDataLength = modelVertexBuffer.Position = modelVertexBuffer.Length;
            int vertexPointerBufferLength = modelIndirectVertexBuffer.Length = Round(12 * vertexOffsets.Count, 32);
            int indexPointerBufferLength = modelIndirectIndexBuffer.Length = 12 * indexOffsets.Count;
            uint totalVertexBufferLength = (uint)(modelVertexBuffer.Length);
            uint totalIndexBufferLength = (uint)modelIndexBuffer.Length;
            uint magicIndexLength = (uint)(Round(indexBuffer.Length, 0x20) - mHeaderLength);
            uint memory = (uint)(mIndexMagic + magicIndexLength + totalVertexBufferLength + totalIndexBufferLength + vertexPointerBufferLength + indexPointerBufferLength);
            uint stringMemory = (uint)(mIndexMagic - mHeaderLength);
            uint modelIndexMemory = (uint)(mIndexMagic + magicIndexLength + totalVertexBufferLength + vertexPointerBufferLength + indexPointerBufferLength);
            uint modelVertexMemory = (uint)(mIndexMagic + magicIndexLength + vertexPointerBufferLength);
            uint modelIndexPointerMemory = (uint)(mIndexMagic + magicIndexLength + totalVertexBufferLength + vertexPointerBufferLength);
            uint modelVertexPointerMemory = (uint)(mIndexMagic + magicIndexLength);
            #endregion
            #region Create the model pointer buffers and update the tag strings
            // These will write the model pointer buffers
            Status("Streaming model pointer buffers...");
            for (int x = 0; x < vertexOffsets.Count; x++)
            {
                indirectVertexOffsets.Add((uint)(modelIndirectVertexBuffer.Position + modelVertexPointerMemory));
                vertexOffsets[x] = (uint)vertexOffsets[x] + modelVertexMemory;
                modelIndirectVertexBuffer.Write((uint)0xcacacaca);
                modelIndirectVertexBuffer.Write((uint)vertexOffsets[x]);
                modelIndirectVertexBuffer.Write((uint)0xcacacaca);
            }
            for (int x = 0; x < indexOffsets.Count; x++)
            {
                indirectIndexOffsets.Add((uint)(modelIndirectIndexBuffer.Position + modelIndexPointerMemory));
                indexOffsets[x] = (uint)indexOffsets[x] + modelIndexMemory;
                modelIndirectIndexBuffer.Write((uint)0xcacacaca);
                modelIndirectIndexBuffer.Write((uint)indexOffsets[x]);
                modelIndirectIndexBuffer.Write((uint)0xcacacaca);
            }
            // We need to add our memory offset to the string offsets now
            Status("Updating string pointers...");
            for (int x = 0; x < count; x++)
                stringOffsets[x] += stringMemory;
            // This will set the tag string offsets to the tag library, so they can be written to tag references
            tags.StringOffsets = stringOffsets;
            #endregion
            #region Shader chicago-ize all extended shaders
            Status("Converting extended shader tags...");
            Xpe scex = structs["scex"];
            Xpe schi = structs["schi"];
            int twoStageMapIndex = scex.IndexByName("2 stage maps");
            int twoStageMapCount = scex.FieldByName("2 stage maps").Children.Length;
            for (int x = 0; x < count; x++)
            {
                if (tags.GetClass(x) == "scex")
                {
                    tags.Recommission(x, "schi");
#if TWOSTAGE
					tags[x]["4 stage maps"] = tags[x]["2 stage maps"];
#endif
                    tags[x]["2 stage maps"] = Array.CreateInstance(typeof(object), 0, twoStageMapCount);
                }
            }
            scex.HeaderLength = 108;
            scex.FieldByName("extra flags").Offset = 96;
            scex.FieldByName("i don't belong here").Size = 0;
            scex.FieldByName("i don't belong here").Offset = 38;
            #endregion
            #region Buffer bsp tags, calculate bsp magic and size (DANGER: MESSY CODE!)
            int previousLength = 0;
            Xpe sbsp = structs["sbsp"];
            ArrayList bspSize = new ArrayList();
            ArrayList bspOffset = new ArrayList();
            ArrayList bspPointers = new ArrayList();
            ArrayList bspMetaOffset = new ArrayList();
            ArrayList bspMaterialList = new ArrayList();
            ArrayList vertexIndirects = new ArrayList();
            ArrayList vertexIndirectOffsets = new ArrayList();
            ArrayList lightmapIndirects = new ArrayList();
            ArrayList lightmapIndirectOffsets = new ArrayList();
            for (int x = 0; x < count; x++)
            {
                if (tags.GetClass(x) == "sbsp")
                {
                    Status("Streaming structure bsp '" + tags.GetName(x) + ".scenario_structure_bsp'");
                    ArrayList cPointerList = new ArrayList();
                    ArrayList cDestinationList = new ArrayList();
                    ArrayList vertexIndirect = new ArrayList();
                    ArrayList vertexIndirectOffset = new ArrayList();
                    ArrayList lightmapIndirect = new ArrayList();
                    ArrayList lightmapIndirectOffset = new ArrayList();
                    bspBuffer.Position = bspBuffer.Length = Round(bspBuffer.Length);
                    bspOffset.Add((uint)bspBuffer.Position);

                    // Create the bsp header
                    int bspMaterialCount = 0;
                    Array bspLightmaps = tags[x]["structure lightmaps"] as Array;
                    int lmCount = bspLightmaps.GetLength(0);
                    Xpe.Field cLightmap = sbsp.FieldByName("structure lightmaps");

                    for (int y = 0; y < lmCount; y++)
                    {
                        Array bspMaterials = bspLightmaps.GetValue(y, cLightmap["structure materials"]) as Array;
                        int matCount = bspMaterials.GetLength(0);
                        bspMaterialCount += matCount;
                    }

                    // Write obviously unfinished bsp header
                    bspBuffer.Write(0);
                    bspBuffer.Write(0);
                    bspBuffer.Write(0);
                    bspBuffer.Write(0);
                    bspBuffer.Write(0);
                    bspBuffer.Write(0x73627370); // 'sbsp'
                    int[] bspAuxInfo = new int[bspMaterialCount * 6];
                    for (int y = 0; y < bspMaterialCount; y++)
                    {
                        bspAuxInfo[(bspMaterialCount - y - 1) * 6 + 4] = bspBuffer.Position;
                        bspBuffer.Write((uint)0xcacacaca);
                        vertexIndirect.Add(bspBuffer.Position);
                        bspBuffer.Write(0);
                        bspBuffer.Write((uint)0xcacacaca);
                    }
                    for (int y = 0; y < bspMaterialCount; y++)
                    {
                        bspAuxInfo[(bspMaterialCount - y - 1) * 6 + 5] = bspBuffer.Position;
                        bspBuffer.Write((uint)0xcacacaca);
                        lightmapIndirect.Add(bspBuffer.Position);
                        bspBuffer.Write(0);
                        bspBuffer.Write((uint)0xcacacaca);
                    }
                    bspMaterialList.Add(bspMaterialCount);
                    bspMaterialCount = 0;

                    // Write bsp vertices
                    for (int y = 0; y < lmCount; y++)
                    {
                        Array bspMaterials = bspLightmaps.GetValue(y, cLightmap["structure materials"]) as Array;
                        int matCount = bspMaterials.GetLength(0);
                        Xpe.Field cMaterial = cLightmap.FieldByName("structure materials");
                        bool noLightmap = (short)bspLightmaps.GetValue(y, cLightmap["bitmap"]) < 0;

                        for (int z = 0; z < matCount; z++)
                        {
                            Tag.DataContainer cvdc = bspMaterials.GetValue(z, cMaterial["compressed vertices"]) as Tag.DataContainer;
                            Tag.DataContainer uvdc = bspMaterials.GetValue(z, cMaterial["uncompressed vertices"]) as Tag.DataContainer;
                            Array bspCompressedVertices = cvdc.data;
                            if (bspCompressedVertices.GetLength(0) == 0)
                                bspCompressedVertices = CompressBspVertices(uvdc.data, !noLightmap);
                            int vertexDataLen = bspCompressedVertices.GetLength(0);
                            if (noLightmap)
                            {
                                bspAuxInfo[bspMaterialCount * 6] = vertexDataLen / 32;
                                bspAuxInfo[bspMaterialCount * 6 + 3] = 0;
                            }
                            else
                            {
                                bspAuxInfo[bspMaterialCount * 6] = vertexDataLen / 40;
                                bspAuxInfo[bspMaterialCount * 6 + 3] = bspAuxInfo[bspMaterialCount * 6];
                            }
                            bspAuxInfo[bspMaterialCount * 6 + 1] = vertexDataLen;
                            int vertexPadLen = Pad(bspBuffer.Length, 32);
                            for (int pb = 0; pb < vertexPadLen; pb++)
                                bspBuffer.Write((byte)0xca);
                            vertexIndirectOffset.Add((uint)bspBuffer.Position);
                            lightmapIndirectOffset.Add((uint)(bspBuffer.Position + (bspAuxInfo[bspMaterialCount * 6] * 32)));
                            bspAuxInfo[(bspMaterialCount++) * 6 + 2] = bspBuffer.Position;
                            //for (int i = 0; i < vertexDataLen; i++)

                            //HAX
                            bspBuffer.Write(bspCompressedVertices as byte[]);
                            //bspBuffer.Length += bspCompressedVertices.Length;
                            //bspBuffer.Position = bspBuffer.Length;

                            // Cull this data from the bsp block
                            // TODO: Create a better way to do this without scarring tags
                            //bspMaterials.SetValue(new Tag.DataContainer(cvdc.variable, new byte[0]), z, cMaterial["compressed vertices"]);
                            //bspMaterials.SetValue(new Tag.DataContainer(uvdc.variable, new byte[0]), z, cMaterial["uncompressed vertices"]);
                        }
                    }

                    lightmapIndirects.Add(lightmapIndirect);
                    lightmapIndirectOffsets.Add(lightmapIndirectOffset);
                    vertexIndirects.Add(vertexIndirect);
                    vertexIndirectOffsets.Add(vertexIndirectOffset);
                    bspMetaOffset.Add((uint)bspBuffer.Position);
                    predictor.GeneratePRs(tags[x]);
                    tags[x].Write(bspBuffer, tags, cPointerList, cDestinationList, bspAuxInfo);
                    bspPointers.Add(new PointerList(cPointerList, cDestinationList));
                    bspBuffer.Length = Round(bspBuffer.Length, 0x800);
                    bspSize.Add(bspBuffer.Length - previousLength);
                    bspBuffer.Position = previousLength = bspBuffer.Length;
                }
            }
            int sbspCount = 0;
            ArrayList bspMagic = new ArrayList();
            for (int x = 0; x < count; x++)
                if (tags.GetClass(x) == "sbsp")
                    bspMagic.Add((uint)((uint)BspMagic.Xbox - (int)bspSize[sbspCount++]));
            #endregion
            #region Update all bsp related pointers with newly acquired bsp memory offset information
            // Get an array of bsp data from the scenario
            Status("Offsetting structure bsp pointers...");
            Xpe scnr = structs["scnr"];
            Tag scenario = tags[0];
            Array bspBlockData = scenario["structure bsps"] as Array;
            Xpe.Field bspBlockField = scnr.FieldByName("structure bsps");
            for (int x = 0; x < sbspCount; x++)
            {
                // Fix pointers
                int bspMatListX = (int)bspMaterialList[x];
                uint bspMagicX = (uint)((uint)bspMagic[x] - (uint)bspOffset[x]);
                ArrayList vertexIndirectX = vertexIndirects[x] as ArrayList;
                ArrayList vertexIndirectOffsetX = vertexIndirectOffsets[x] as ArrayList;
                ArrayList lightmapIndirectX = lightmapIndirects[x] as ArrayList;
                ArrayList lightmapIndirectOffsetX = lightmapIndirectOffsets[x] as ArrayList;
                PointerList pl = (PointerList)bspPointers[x];
                for (int y = 0; y < pl.Count; y++)
                {
                    bspBuffer.Position = (int)pl.Locations[y];
                    bspBuffer.Write(bspMagicX + (uint)pl.Destinations[y]);
                }
                for (int y = 0; y < bspMatListX; y++)
                {
                    bspBuffer.Position = (int)vertexIndirectX[bspMatListX - y - 1];
                    bspBuffer.Write(bspMagicX + (uint)vertexIndirectOffsetX[y]);
                }
                for (int y = 0; y < bspMatListX; y++)
                {
                    bspBuffer.Position = (int)lightmapIndirectX[bspMatListX - y - 1];
                    bspBuffer.Write(bspMagicX + (uint)lightmapIndirectOffsetX[y]);
                }

                // Write header
                bspBuffer.Position = (int)(uint)bspOffset[x];
                bspBuffer.Write(bspMagicX + (uint)bspMetaOffset[x]);
                bspBuffer.Write(bspMatListX);
                bspBuffer.Write((uint)(bspMagicX + 24 + (uint)bspOffset[x]));
                bspBuffer.Write(bspMatListX);
                bspBuffer.Write((uint)(bspMagicX + 24 + (bspMatListX * 12) + (uint)bspOffset[x]));

                // Write to memory the scenario bsp data
                bspBlockData.SetValue((uint)bspOffset[x] + 0x800, x, bspBlockField["bsp offset"]);
                bspBlockData.SetValue(bspSize[x], x, bspBlockField["bsp size"]);
                bspBlockData.SetValue(bspMagicX + (uint)bspOffset[x], x, bspBlockField["bsp magic"]);
            }
            // That was relatively painless... Right?
            #endregion
            #region Start building the data table and metadata buffer
            // Note: at this point, anything to do with bsp should be done
            int currentModelIndex = 0;
            local.Length = Round(0x800 + bspBuffer.Length, 0x200);
            uint[] metaOffsets = new uint[count];
            for (int x = 0; x < count; x++)
            {
                Xpe bitm = structs["bitm"];
                Xpe snd = structs["snd!"];
                Xpe mode = structs["mode"];
                Xpe mod2 = structs["mod2"];

                if (tags.GetClass(x) == "sbsp")
                {
                    metaOffsets[x] = 0;
                    continue;
                }

                Status("Streaming tag '" + tags.GetName(x) + '.' + structs[tags.GetClass(x)].Extension + '\'');
                metaBuffer.Position = metaBuffer.Length = Round(metaBuffer.Length, 4);
                metaOffsets[x] = (uint)metaBuffer.Position;
                if (tags.GetClass(x) == "bitm")
                {
                    Array pixelData = tags[x]["processed pixel data"] as Array;
                    int pixelLength = pixelData.GetLength(0);
                    local.Position = local.Length = Round(local.Length, 0x200);
                    int[] pixelOffset = new int[1] { local.Length };

                    //// Swizzle the texture if necessary
                    Array bitmaps = tags[x]["bitmaps"] as Array;
                    int bmc = bitmaps.GetLength(0);
                    bool[] swizzled = new bool[bmc];
                    for (int b = 0; b < bmc; b++)
                    {
                        swizzled[b] = false;
                        byte tflags = (byte)(short)bitmaps.GetValue(b, 6);
                        if ((tflags & 8) > 0)
                            continue;
                        short tformat = (short)bitmaps.GetValue(b, 5);
                        if ((tformat > 13 && tformat < 17) || tformat == 9)
                            continue;
                        short ttype = (short)bitmaps.GetValue(b, 4);
                        short mips = (short)bitmaps.GetValue(b, 8);
                        int Offset = (int)bitmaps.GetValue(b, 10);
                        if (mips > 0)
                        {
                            int mipSize = 0;
                            for (int m = 0; m < mips; m++)
                            {
                                double width = (short)bitmaps.GetValue(b, 1) / (short)Math.Pow(2.0, (double)m);
                                double height = (short)bitmaps.GetValue(b, 2) / (short)Math.Pow(2.0, (double)m);
                                pixelData = Swizzler.Swizzle(pixelData as byte[], Offset + mipSize, (short)width, (short)height, (short)bitmaps.GetValue(b, 3), BitmapBpp(tformat), false) as Array;
                                mipSize += Tag.CalculateMipmapSize((short)bitmaps.GetValue(b, 1), (short)bitmaps.GetValue(b, 2), (short)bitmaps.GetValue(b, 3), ttype, tformat, (short)m);
                            }
                        }
                        else
                        {
                            pixelData = Swizzler.Swizzle(pixelData as byte[], Offset, (short)bitmaps.GetValue(b, 1), (short)bitmaps.GetValue(b, 2), (short)bitmaps.GetValue(b, 3), BitmapBpp(tformat), false) as Array;
                        }
                        bitmaps.SetValue((short)(tflags | 0x8), b, 6); // TODO: Figure out what a lot of these are...
                        swizzled[b] = true;
                        //bitmaps.SetValue(new byte[4] { 0xff, 0xff, 0xff, 0xff }, b, 12);
                        //bitmaps.SetValue(new byte[8] { 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x27, 0x02 }, b, 13);
                    }

                    local.Write(pixelData as byte[]);
                    tags[x].Write(metaBuffer, memory, tags, pixelOffset);
                    //tags[x]["processed pixel data"] = pixelData;
                    // Set flags back
                    for (int b = 0; b < bmc; b++)
                    {
                        if (swizzled[b])
                        {
                            byte tflags = (byte)(short)bitmaps.GetValue(b, 6);
                            if ((tflags & 8) > 0)
                                bitmaps.SetValue((short)(tflags ^ 0x8), b, 6);
                        }
                    }
                }
                else if (tags.GetClass(x) == "snd!")
                {
                    Array pitchRanges = tags[x]["pitch ranges#pitch ranges allow multiple samples to represent the same sound at different pitches"] as Array;
                    int pitchRangeCount = pitchRanges.GetLength(0);
                    Xpe.Field pitchRange = snd.FieldByName("pitch ranges#pitch ranges allow multiple samples to represent the same sound at different pitches");
                    int totalPermutationCount = 0;

                    for (int u = 0; u < pitchRangeCount; u++)
                    {
                        Array permutations = pitchRanges.GetValue(u, pitchRange["permutations#permutations represent equivalent variations of this sound."]) as Array;
                        totalPermutationCount += permutations.GetLength(0);
                    }

                    int[] soundAux = new int[totalPermutationCount * 2];
                    totalPermutationCount = 0;

                    for (int u = 0; u < pitchRangeCount; u++)
                    {
                        Array permutations = pitchRanges.GetValue(u, pitchRange["permutations#permutations represent equivalent variations of this sound."]) as Array;
                        int permutationCount = permutations.GetLength(0);
                        Xpe.Field permutation = pitchRange.FieldByName("permutations#permutations represent equivalent variations of this sound.");

                        for (int v = 0; v < permutationCount; v++)
                        {
                            local.Position = local.Length = Round(local.Length, 0x200);

                            Array samples = permutations.GetValue(v, permutation["samples#sampled sound data"]) as Array;
                            int byteCount = samples.GetLength(0);
                            soundAux[totalPermutationCount++] = byteCount;
                            soundAux[totalPermutationCount++] = local.Length;

                            local.Write(samples as byte[]);
                        }
                    }

                    tags[x].Write(metaBuffer, memory, tags, soundAux);
                }
                else if (tags.GetClass(x) == "mode" || tags.GetClass(x) == "mod2")
                {
                    Xpe mXpe = null;
                    if (tags.GetClass(x) == "mode")
                        mXpe = mode;
                    else
                        mXpe = mod2;

                    Array geometries = tags[x]["geometries"] as Array;
                    int geometryCount = geometries.GetLength(0);
                    Xpe.Field geometry = mXpe.FieldByName("geometries");
                    int totalPartCount = 0;

                    for (int u = 0; u < geometryCount; u++)
                    {
                        Array parts = geometries.GetValue(u, geometry["parts"]) as Array;
                        totalPartCount += parts.GetLength(0);
                    }

                    int[] modelAux = new int[totalPartCount * 5];
                    totalPartCount = 0;

                    for (int u = 0; u < geometryCount; u++)
                    {
                        Array parts = geometries.GetValue(u, geometry["parts"]) as Array;
                        int partCount = parts.GetLength(0);
                        Xpe.Field part = geometry.FieldByName("parts");

                        for (int v = 0; v < partCount; v++)
                        {
                            modelAux[totalPartCount++] = (int)indexCounts[currentModelIndex];
                            modelAux[totalPartCount++] = ToIntByReference((uint)indexOffsets[currentModelIndex]);
                            modelAux[totalPartCount++] = (int)vertexCounts[currentModelIndex];
                            modelAux[totalPartCount++] = ToIntByReference((uint)indirectVertexOffsets[currentModelIndex]);
                            modelAux[totalPartCount++] = ToIntByReference((uint)indirectIndexOffsets[currentModelIndex++]);
                        }
                    }

                    tags[x].Write(metaBuffer, memory, tags, modelAux);
                }
                else
                {
                    predictor.GeneratePRs(tags[x]);
                    tags[x].Write(metaBuffer, memory, tags, null);
                }
            }
            #endregion
            #region Pad map sections
            bspBuffer.Length = Round(bspBuffer.Length, 0x200);
            local.Length = Round(local.Length, 0x200);
            #endregion
            #region Finish the object index
            Status("Completing object index...");
            indexBuffer.Position = 16;
            indexBuffer.Write(vertexOffsets.Count);
            indexBuffer.Write(modelVertexPointerMemory);
            indexBuffer.Write(indexOffsets.Count);
            indexBuffer.Write(modelIndexPointerMemory);

            indexBuffer.Position = (int)HeaderLengths.Xbox;
            for (int x = 0; x < count; x++)
            {
                Xpe tagXpe = tags[x].Xpe;
                if (tagXpe.FourCC == "mod2")
                    indexBuffer.WriteFourCC("mode");
                else if (tagXpe.FourCC == "scex")
                    indexBuffer.WriteFourCC("schi");
                else
                    indexBuffer.WriteFourCC(tagXpe.FourCC);
                indexBuffer.WriteFourCC(tagXpe.ParentFourCC);
                indexBuffer.WriteFourCC(tagXpe.GrandparentFourCC);
                indexBuffer.Write(tags.GetID(x));
                indexBuffer.Write(stringOffsets[x]);
                if (tagXpe.FourCC == "sbsp")
                    indexBuffer.Write(0);
                else
                    indexBuffer.Write((uint)(metaOffsets[x] + memory));
                indexBuffer.Write(0);
                indexBuffer.Write(0);
            }
            #endregion
            #region Write the map header
            Status("Generating map header...");
            header.Length = 0x800;
            header.Position = 0;
            header.Write(0x68656164); // 'head'
            header.Write((int)MapFormat.Xbox);
            header.Write(Round(local.Length + indexBuffer.Length + modelIndirectVertexBuffer.Length + modelVertexBuffer.Length + modelIndirectIndexBuffer.Length + modelIndexBuffer.Length + metaBuffer.Length));
            header.Write(0);
            header.Write(local.Length);
            header.Write(indexBuffer.Length + modelIndirectVertexBuffer.Length + modelVertexBuffer.Length + modelIndirectIndexBuffer.Length + modelIndexBuffer.Length + metaBuffer.Length);
            header.Write(0);
            header.Write(0);
            header.Write(internalname, false);
            header.Position = 0x40;
            if (pal)
                header.Write(MapBuildVersion.PALXbox, false);
            else
                header.Write(MapBuildVersion.NTSCXbox, false);
            header.Position = 0x60;
            header.Write((int)usage);
            for (int x = 0; x < 486; x++)
                header.Write(0);
            header.Write(0x666f6f74); // 'foot'
            #endregion
            if (!compress)
            {
                #region Assemble map file from buffers (No Compression)
                local.Position = 0;

                Status("Flushing header...");
                local.Write((header.BaseStream as MemoryStream).ToArray());
                header.Close(); header = null;

                Status("Flushing structure bsps...");
                local.Write((bspBuffer.BaseStream as MemoryStream).ToArray());
                bspBuffer.Close(); bspBuffer = null;
                local.Position = local.Length;

                Status("Flushing object index...");
                local.Write((indexBuffer.BaseStream as MemoryStream).ToArray());
                indexBuffer.Close(); indexBuffer = null;

                Status("Flushing indirect vertex buffer...");
                local.Write((modelIndirectVertexBuffer.BaseStream as MemoryStream).ToArray());
                modelIndirectVertexBuffer.Close(); modelIndirectVertexBuffer = null;

                Status("Flushing vertex buffer...");
                local.Write((modelVertexBuffer.BaseStream as MemoryStream).ToArray());
                modelVertexBuffer.Close(); modelVertexBuffer = null;

                Status("Flushing indirect index buffer...");
                local.Write((modelIndirectIndexBuffer.BaseStream as MemoryStream).ToArray());
                modelIndirectIndexBuffer.Close(); modelIndirectIndexBuffer = null;

                Status("Flushing index buffer...");
                local.Write((modelIndexBuffer.BaseStream as MemoryStream).ToArray());
                modelIndexBuffer.Close(); modelIndexBuffer = null;

                Status("Flushing meta buffer...");
                local.Write((metaBuffer.BaseStream as MemoryStream).ToArray());
                metaBuffer.Close(); metaBuffer = null;

                Status("Padding map...");
                local.Length = Round(local.Length);
                if (local.Length < 0x2f00000)
                    local.Length = 0x2f00000;
                Status("Freeing allocated memory...");
                local.Close(); local = null;
                GC.Collect();
                Status("Finished.");
                #endregion
            }
            else
            {
                #region Assemble map file from buffers (Compression)
                local.Position = 0;

                Status("Flushing header...");
                local.Write((header.BaseStream as MemoryStream).ToArray());
                header.Close(); header = null;

                Status("Flushing structure bsps...");
                local.Write((bspBuffer.BaseStream as MemoryStream).ToArray());
                bspBuffer.Close(); bspBuffer = null;
                local.Position = local.Length;

                Status("Flushing object index...");
                local.Write((indexBuffer.BaseStream as MemoryStream).ToArray());
                indexBuffer.Close(); indexBuffer = null;

                Status("Flushing indirect vertex buffer...");
                local.Write((modelIndirectVertexBuffer.BaseStream as MemoryStream).ToArray());
                modelIndirectVertexBuffer.Close(); modelIndirectVertexBuffer = null;

                Status("Flushing vertex buffer...");
                local.Write((modelVertexBuffer.BaseStream as MemoryStream).ToArray());
                modelVertexBuffer.Close(); modelVertexBuffer = null;

                Status("Flushing indirect index buffer...");
                local.Write((modelIndirectIndexBuffer.BaseStream as MemoryStream).ToArray());
                modelIndirectIndexBuffer.Close(); modelIndirectIndexBuffer = null;

                Status("Flushing index buffer...");
                local.Write((modelIndexBuffer.BaseStream as MemoryStream).ToArray());
                modelIndexBuffer.Close(); modelIndexBuffer = null;

                Status("Flushing meta buffer...");
                local.Write((metaBuffer.BaseStream as MemoryStream).ToArray());
                metaBuffer.Close(); metaBuffer = null;


                Status("Compressing map...");
                BinaryReader localBr = new BinaryReader(local.BaseStream);
                localBr.BaseStream.Position = 0x800;

                byte[] decompressed = localBr.ReadBytes((int)localBr.BaseStream.Length - 0x800);

                //AdlerChecksum ad = new AdlerChecksum();
                //ad.MakeForBuff(decompressed);


                MemoryStream decoMS = new MemoryStream();
                ICSharpCode.SharpZipLib.Zip.Compression.Streams.DeflaterOutputStream compressed = new ICSharpCode.SharpZipLib.Zip.Compression.Streams.DeflaterOutputStream(decoMS);
                compressed.Write(decompressed, 0, decompressed.Length);
                compressed.Finish();

                local.Position = 0x800;
                local.Length = 0x800;
                local.Write(decoMS.ToArray());
                local.Length = Round(local.Length);

                Status("Freeing allocated memory...");
                compressed.Flush();
                compressed.Close(); compressed = null;
                local.Close(); local = null;
                GC.Collect();
                Status("Finished.");
                #endregion
            }
        }
        public void SaveAsPC(string fn, bool pal, string internalname, bool compress)
        {
            #region Attach streams to buffers and create a 'predictor'
            Status("Preparing to compile...");
            count = tags.Count;
            EndianWriter header = new EndianWriter(new MemoryStream(), ByteOrder.LittleEndian); // holds the map header, dumbass
            EndianWriter bspBuffer = new EndianWriter(new MemoryStream(), ByteOrder.LittleEndian); // holds bsp header, vertices, triangles, and meta
            EndianWriter indexBuffer = new EndianWriter(new MemoryStream(), ByteOrder.LittleEndian); // holds the map object index and filenames
            EndianWriter modelIndirectVertexBuffer = new EndianWriter(new MemoryStream(), ByteOrder.LittleEndian); // holds model vertex pointers
            EndianWriter modelVertexBuffer = new EndianWriter(new MemoryStream(), ByteOrder.LittleEndian); // holds model vertices
            EndianWriter modelIndirectIndexBuffer = new EndianWriter(new MemoryStream(), ByteOrder.LittleEndian); // holds model index pointers
            EndianWriter modelIndexBuffer = new EndianWriter(new MemoryStream(), ByteOrder.LittleEndian); // holds model indices
            EndianWriter metaBuffer = new EndianWriter(new MemoryStream(), ByteOrder.LittleEndian); // holds tag metadata
            EndianWriter local = new EndianWriter(new FileStream(fn, FileMode.Create, FileAccess.ReadWrite, FileShare.None), ByteOrder.LittleEndian); // Map is outputted through this stream; also holds textures and sounds at compiletime
            Predictor predictor = new Predictor(tags);
            #endregion
            #region Prepare the model vertex and index buffers, cull compressed model vertices
            Status("Streaming model vertex and index buffers; culling compressed model vertices...");
            ArrayList indirectVertexOffsets = new ArrayList();
            ArrayList indirectIndexOffsets = new ArrayList();
            ArrayList vertexOffsets = new ArrayList();
            ArrayList indexOffsets = new ArrayList();
            ArrayList vertexCounts = new ArrayList();
            ArrayList indexCounts = new ArrayList();
            for (int x = 0; x < count; x++)
            {
                Xpe mode = structs["mode"];
                Xpe mod2 = structs["mod2"];
                Xpe xModel = null;
                if (tags.GetClass(x) == "mod2" || tags.GetClass(x) == "mode")
                {
                    if (tags.GetClass(x) == "mod2")
                        xModel = mod2;
                    else
                        xModel = mode;

                    Array geometry = tags[x]["geometries"] as Array;
                    int geometries = geometry.GetLength(0);
                    for (int y = 0; y < geometries; y++)
                    {
                        Array part = geometry.GetValue(y, xModel.FieldByName("geometries")["parts"]) as Array;
                        int parts = part.GetLength(0);
                        for (int z = 0; z < parts; z++)
                        {
                            // These will speed this method up by quite a bit, and make it much more readable
                            Xpe.Field cPart = xModel.FieldByName("geometries").FieldByName("parts");
                            Xpe.Field cVertex = cPart.FieldByName("compressed vertices");
                            Xpe.Field cFace = cPart.FieldByName("triangles");

                            // Write the compressed vertices to the vertex buffer, or if they do not exist, compress the uncompressed vertices and use them instead
                            Array vertex = part.GetValue(z, cPart["compressed vertices"]) as Array;
                            bool uncompressed = false;
                            int verts = vertex.GetLength(0);
                            vertexCounts.Add(verts);
                            if (uncompressed = (verts == 0))
                            {
                                cVertex = cPart.FieldByName("uncompressed vertices");
                                vertex = part.GetValue(z, cPart["uncompressed vertices"]) as Array;
                                verts = vertex.GetLength(0);
                            }
                            modelVertexBuffer.Position = modelVertexBuffer.Length = Round(modelVertexBuffer.Length, 68);
                            vertexOffsets.Add((uint)modelVertexBuffer.Length);
                            for (int i = 0; i < verts; i++)
                            {
                                modelVertexBuffer.Write((float)(vertex.GetValue(i, cVertex["position"]) as Array).GetValue(0));
                                modelVertexBuffer.Write((float)(vertex.GetValue(i, cVertex["position"]) as Array).GetValue(1));
                                modelVertexBuffer.Write((float)(vertex.GetValue(i, cVertex["position"]) as Array).GetValue(2));
                                if (uncompressed)
                                {
                                    modelVertexBuffer.Write((float)(vertex.GetValue(i, cVertex["normal"]) as Array).GetValue(0));
                                    modelVertexBuffer.Write((float)(vertex.GetValue(i, cVertex["normal"]) as Array).GetValue(1));
                                    modelVertexBuffer.Write((float)(vertex.GetValue(i, cVertex["normal"]) as Array).GetValue(2));
                                    modelVertexBuffer.Write((float)(vertex.GetValue(i, cVertex["binormal"]) as Array).GetValue(0));
                                    modelVertexBuffer.Write((float)(vertex.GetValue(i, cVertex["binormal"]) as Array).GetValue(1));
                                    modelVertexBuffer.Write((float)(vertex.GetValue(i, cVertex["binormal"]) as Array).GetValue(2));
                                    modelVertexBuffer.Write((float)(vertex.GetValue(i, cVertex["tangent"]) as Array).GetValue(0));
                                    modelVertexBuffer.Write((float)(vertex.GetValue(i, cVertex["tangent"]) as Array).GetValue(1));
                                    modelVertexBuffer.Write((float)(vertex.GetValue(i, cVertex["tangent"]) as Array).GetValue(2));
                                    modelVertexBuffer.Write((float)(vertex.GetValue(i, cVertex["texture coords"]) as Array).GetValue(0));
                                    modelVertexBuffer.Write((float)(vertex.GetValue(i, cVertex["texture coords"]) as Array).GetValue(1));
                                    modelVertexBuffer.Write((short)vertex.GetValue(i, cVertex["node0 index"]));
                                    modelVertexBuffer.Write((short)vertex.GetValue(i, cVertex["node1 index"]));
                                    modelVertexBuffer.Write((float)vertex.GetValue(i, cVertex["node0 weight"]));
                                }
                                else
                                {
                                    modelVertexBuffer.Write((int)(vertex.GetValue(i, cVertex["normal[11.11.10-bit]"])));
                                    modelVertexBuffer.Write((int)(vertex.GetValue(i, cVertex["binormal[11.11.10-bit]"])));
                                    modelVertexBuffer.Write((int)(vertex.GetValue(i, cVertex["tangent[11.11.10-bit]"])));
                                    modelVertexBuffer.Write((short)(vertex.GetValue(i, cVertex["texture coordinate u[16-bit]"])));
                                    modelVertexBuffer.Write((short)(vertex.GetValue(i, cVertex["texture coordinate v[16-bit]"])));
                                    modelVertexBuffer.Write((byte)(vertex.GetValue(i, cVertex["node0 index(x3)"])));
                                    modelVertexBuffer.Write((byte)(vertex.GetValue(i, cVertex["node1 index(x3)"])));
                                    modelVertexBuffer.Write((short)(vertex.GetValue(i, cVertex["node0 weight[16-bit]"])));
                                }
                            }

                            // Write indices to index buffer
                            Array triangle = part.GetValue(z, cPart["triangles"]) as Array;
                            int triangles = triangle.GetLength(0);
                            modelIndexBuffer.Position = modelIndexBuffer.Length = Round(modelIndexBuffer.Length, 16);
                            indexOffsets.Add((uint)modelIndexBuffer.Length);
                            short difference = 2;
                            for (int i = 0; i < triangles; i++)
                            {
                                short a = (short)triangle.GetValue(i, cFace["vertex0 index"]);
                                short b = (short)triangle.GetValue(i, cFace["vertex1 index"]);
                                short c = (short)triangle.GetValue(i, cFace["vertex2 index"]);
                                if (i == triangles - 1)
                                {
                                    if (a == -1)
                                        difference += 3;
                                    else if (b == -1)
                                        difference += 2;
                                    else if (c == -1)
                                        difference += 1;
                                }
                                modelIndexBuffer.Write(a);
                                modelIndexBuffer.Write(b);
                                modelIndexBuffer.Write(c);
                            }
                            indexCounts.Add((int)(triangles * 3 - difference));

                            // Cull these structures from the tag
                            // TODO: create better way to cull model data that doesn't permanently scar the tags
                            //part.SetValue(Array.CreateInstance(typeof(object), 0, cPart.FieldByName("uncompressed vertices").Children.Length), z, cPart["uncompressed vertices"]);
                            //part.SetValue(Array.CreateInstance(typeof(object), 0, cPart.FieldByName("compressed vertices").Children.Length), z, cPart["compressed vertices"]);
                            //part.SetValue(Array.CreateInstance(typeof(object), 0, cFace.Children.Length), z, cPart["triangles"]);
                        }
                    }
                }

                // This 'converts' all pc 'mod2' tags into xbox 'mode' tags
                // TODO: Change this because it scars the structure file!! This only works if you close the application between builds!
                Xpe.Field geometryEdit = mod2.FieldByName("geometries");
                Xpe.Field geometryParts = geometryEdit.FieldByName("parts");

                //geometryParts.Size = 104;
                //geometryParts.Children[15].Size = 0;
            }
            #endregion
            #region Prepare the index
            Status("Writing an index shell...");
            indexBuffer.Write((uint)IndexMagic.CE);
            indexBuffer.Write(tags.GetID(0));
            indexBuffer.Write(0);
            indexBuffer.Write(count);
            indexBuffer.Position = 32;
            indexBuffer.Write(0x74616773); // 'tags'
            indexBuffer.Position = indexBuffer.Length += count * 32;
            uint[] stringOffsets = new uint[count];
            for (int x = 0; x < count; x++)
            {
                stringOffsets[x] = (uint)indexBuffer.Position;
                indexBuffer.Write(tags.GetName(x), true);
            }
            indexBuffer.Position = indexBuffer.Length = Round(indexBuffer.Length, 32);
            modelIndexBuffer.Position = modelIndexBuffer.Length = Round(modelIndexBuffer.Length, 16);
            modelVertexBuffer.Position = modelVertexBuffer.Length = Round(modelVertexBuffer.Length, 32);
            #endregion
            #region Calculate "magic" offsets
            // 'memory' is a handy little value that we can add to any post-model offset to simulate "magic", no matter what we do anywhere else now
            uint mIndexMagic = (uint)IndexMagic.CE;
            int mHeaderLength = (int)HeaderLengths.CE;
            int vertexDataLength = modelVertexBuffer.Position = modelVertexBuffer.Length;
            int vertexPointerBufferLength = modelIndirectVertexBuffer.Length = Round(12 * vertexOffsets.Count, 32);
            int indexPointerBufferLength = modelIndirectIndexBuffer.Length = 12 * indexOffsets.Count;
            uint totalVertexBufferLength = (uint)(modelVertexBuffer.Length);
            uint totalIndexBufferLength = (uint)modelIndexBuffer.Length;
            uint magicIndexLength = (uint)(Round(indexBuffer.Length, 0x20) - mHeaderLength);
            uint memory = (uint)(mIndexMagic + magicIndexLength + totalVertexBufferLength + totalIndexBufferLength + vertexPointerBufferLength + indexPointerBufferLength);
            uint stringMemory = (uint)(mIndexMagic - mHeaderLength);
            uint modelIndexMemory = (uint)(mIndexMagic + magicIndexLength + totalVertexBufferLength + vertexPointerBufferLength + indexPointerBufferLength);
            uint modelVertexMemory = (uint)(mIndexMagic + magicIndexLength + vertexPointerBufferLength);
            uint modelIndexPointerMemory = (uint)(mIndexMagic + magicIndexLength + totalVertexBufferLength + vertexPointerBufferLength);
            uint modelVertexPointerMemory = (uint)(mIndexMagic + magicIndexLength);
            #endregion
            #region Create the model pointer buffers and update the tag strings
            // These will write the model pointer buffers
            Status("Streaming model pointer buffers...");
            for (int x = 0; x < vertexOffsets.Count; x++)
            {
                indirectVertexOffsets.Add((uint)(modelIndirectVertexBuffer.Position + modelVertexPointerMemory));
                vertexOffsets[x] = (uint)vertexOffsets[x] + modelVertexMemory;
                modelIndirectVertexBuffer.Write((uint)0xcacacaca);
                modelIndirectVertexBuffer.Write((uint)vertexOffsets[x]);
                modelIndirectVertexBuffer.Write((uint)0xcacacaca);
            }
            for (int x = 0; x < indexOffsets.Count; x++)
            {
                indirectIndexOffsets.Add((uint)(modelIndirectIndexBuffer.Position + modelIndexPointerMemory));
                indexOffsets[x] = (uint)indexOffsets[x] + modelIndexMemory;
                modelIndirectIndexBuffer.Write((uint)0xcacacaca);
                modelIndirectIndexBuffer.Write((uint)indexOffsets[x]);
                modelIndirectIndexBuffer.Write((uint)0xcacacaca);
            }
            // We need to add our memory offset to the string offsets now
            Status("Updating string pointers...");
            for (int x = 0; x < count; x++)
                stringOffsets[x] += stringMemory;
            // This will set the tag string offsets to the tag library, so they can be written to tag references
            tags.StringOffsets = stringOffsets;
            #endregion
            #region Shader chicago-ize all extended shaders
            //Status("Converting extended shader tags...");
            //Xpe scex = structs["scex"];
            //Xpe schi = structs["schi"];
            //int twoStageMapIndex = scex.IndexByName("2 stage maps");
            //int twoStageMapCount = scex.FieldByName("2 stage maps").Children.Length;
            //for (int x = 0; x < count; x++)
            //{
            //    if (tags.GetClass(x) == "scex")
            //    {
            //        tags.Recommission(x, "schi");
            //#if TWOSTAGE
            //					tags[x]["4 stage maps"] = tags[x]["2 stage maps"];
            //#endif
            //                   tags[x]["2 stage maps"] = Array.CreateInstance(typeof(object), 0, twoStageMapCount);
            //               }
            //           }
            //           scex.HeaderLength = 108;
            //           scex.FieldByName("extra flags").Offset = 96;
            //           scex.FieldByName("i don't belong here").Size = 0;
            //           scex.FieldByName("i don't belong here").Offset = 38;
            #endregion
            #region Buffer bsp tags, calculate bsp magic and size (DANGER: MESSY CODE!)
            int previousLength = 0;
            Xpe sbsp = structs["sbsp"];
            ArrayList bspSize = new ArrayList();
            ArrayList bspOffset = new ArrayList();
            ArrayList bspPointers = new ArrayList();
            ArrayList bspMetaOffset = new ArrayList();
            ArrayList bspMaterialList = new ArrayList();
            ArrayList vertexIndirects = new ArrayList();
            ArrayList vertexIndirectOffsets = new ArrayList();
            ArrayList lightmapIndirects = new ArrayList();
            ArrayList lightmapIndirectOffsets = new ArrayList();
            for (int x = 0; x < count; x++)
            {
                if (tags.GetClass(x) == "sbsp")
                {
                    Status("Streaming structure bsp '" + tags.GetName(x) + ".scenario_structure_bsp'");
                    ArrayList cPointerList = new ArrayList();
                    ArrayList cDestinationList = new ArrayList();
                    ArrayList vertexIndirect = new ArrayList();
                    ArrayList vertexIndirectOffset = new ArrayList();
                    ArrayList lightmapIndirect = new ArrayList();
                    ArrayList lightmapIndirectOffset = new ArrayList();
                    bspBuffer.Position = bspBuffer.Length = Round(bspBuffer.Length);
                    bspOffset.Add((uint)bspBuffer.Position);

                    // Create the bsp header
                    int bspMaterialCount = 0;
                    Array bspLightmaps = tags[x]["structure lightmaps"] as Array;
                    int lmCount = bspLightmaps.GetLength(0);
                    Xpe.Field cLightmap = sbsp.FieldByName("structure lightmaps");

                    for (int y = 0; y < lmCount; y++)
                    {
                        Array bspMaterials = bspLightmaps.GetValue(y, cLightmap["structure materials"]) as Array;
                        int matCount = bspMaterials.GetLength(0);
                        bspMaterialCount += matCount;
                    }

                    // Write obviously unfinished bsp header
                    bspBuffer.Write(0);
                    bspBuffer.Write(0);
                    bspBuffer.Write(0);
                    bspBuffer.Write(0);
                    bspBuffer.Write(0);
                    bspBuffer.Write(0x73627370); // 'sbsp'
                    int[] bspAuxInfo = new int[bspMaterialCount * 6];
                    for (int y = 0; y < bspMaterialCount; y++)
                    {
                        bspAuxInfo[(bspMaterialCount - y - 1) * 6 + 4] = bspBuffer.Position;
                        bspBuffer.Write((uint)0xcacacaca);
                        vertexIndirect.Add(bspBuffer.Position);
                        bspBuffer.Write(0);
                        bspBuffer.Write((uint)0xcacacaca);
                    }
                    for (int y = 0; y < bspMaterialCount; y++)
                    {
                        bspAuxInfo[(bspMaterialCount - y - 1) * 6 + 5] = bspBuffer.Position;
                        bspBuffer.Write((uint)0xcacacaca);
                        lightmapIndirect.Add(bspBuffer.Position);
                        bspBuffer.Write(0);
                        bspBuffer.Write((uint)0xcacacaca);
                    }
                    bspMaterialList.Add(bspMaterialCount);
                    bspMaterialCount = 0;

                    // Write bsp vertices
                    for (int y = 0; y < lmCount; y++)
                    {
                        Array bspMaterials = bspLightmaps.GetValue(y, cLightmap["structure materials"]) as Array;
                        int matCount = bspMaterials.GetLength(0);
                        Xpe.Field cMaterial = cLightmap.FieldByName("structure materials");
                        bool noLightmap = (short)bspLightmaps.GetValue(y, cLightmap["bitmap"]) < 0;

                        for (int z = 0; z < matCount; z++)
                        {
                            Tag.DataContainer cvdc = bspMaterials.GetValue(z, cMaterial["compressed vertices"]) as Tag.DataContainer;
                            Tag.DataContainer uvdc = bspMaterials.GetValue(z, cMaterial["uncompressed vertices"]) as Tag.DataContainer;
                            Array bspUncompressedVertices = uvdc.data;
                            if (bspUncompressedVertices.GetLength(0) == 0)
                                bspUncompressedVertices = CompressBspVertices(uvdc.data, !noLightmap);
                            int vertexDataLen = bspUncompressedVertices.GetLength(0);
                            if (noLightmap)
                            {
                                bspAuxInfo[bspMaterialCount * 6] = vertexDataLen / 56;
                                bspAuxInfo[bspMaterialCount * 6 + 3] = 0;
                            }
                            else
                            {
                                bspAuxInfo[bspMaterialCount * 6] = vertexDataLen / 76;
                                bspAuxInfo[bspMaterialCount * 6 + 3] = bspAuxInfo[bspMaterialCount * 6];
                            }
                            bspAuxInfo[bspMaterialCount * 6 + 1] = vertexDataLen;
                            int vertexPadLen = Pad(bspBuffer.Length, 32);
                            for (int pb = 0; pb < vertexPadLen; pb++)
                                bspBuffer.Write((byte)0xca);
                            vertexIndirectOffset.Add((uint)bspBuffer.Position);
                            lightmapIndirectOffset.Add((uint)(bspBuffer.Position + (bspAuxInfo[bspMaterialCount * 6] * 32)));
                            bspAuxInfo[(bspMaterialCount++) * 6 + 2] = bspBuffer.Position;
                            //for (int i = 0; i < vertexDataLen; i++)

                            //HAX
                            bspBuffer.Write(bspUncompressedVertices as byte[]);
                            //bspBuffer.Length += bspCompressedVertices.Length;
                            //bspBuffer.Position = bspBuffer.Length;

                            // Cull this data from the bsp block
                            // TODO: Create a better way to do this without scarring tags
                            //bspMaterials.SetValue(new Tag.DataContainer(cvdc.variable, new byte[0]), z, cMaterial["compressed vertices"]);
                            //bspMaterials.SetValue(new Tag.DataContainer(uvdc.variable, new byte[0]), z, cMaterial["uncompressed vertices"]);
                        }
                    }

                    lightmapIndirects.Add(lightmapIndirect);
                    lightmapIndirectOffsets.Add(lightmapIndirectOffset);
                    vertexIndirects.Add(vertexIndirect);
                    vertexIndirectOffsets.Add(vertexIndirectOffset);
                    bspMetaOffset.Add((uint)bspBuffer.Position);
                    predictor.GeneratePRs(tags[x]);
                    tags[x].Write(bspBuffer, tags, cPointerList, cDestinationList, bspAuxInfo);
                    bspPointers.Add(new PointerList(cPointerList, cDestinationList));
                    bspBuffer.Length = Round(bspBuffer.Length, 0x800);
                    bspSize.Add(bspBuffer.Length - previousLength);
                    bspBuffer.Position = previousLength = bspBuffer.Length;
                }
            }
            int sbspCount = 0;
            ArrayList bspMagic = new ArrayList();
            for (int x = 0; x < count; x++)
                if (tags.GetClass(x) == "sbsp")
                    bspMagic.Add((uint)((uint)BspMagic.Xbox - (int)bspSize[sbspCount++]));
            #endregion
            #region Update all bsp related pointers with newly acquired bsp memory offset information
            // Get an array of bsp data from the scenario
            Status("Offsetting structure bsp pointers...");
            Xpe scnr = structs["scnr"];
            Tag scenario = tags[0];
            Array bspBlockData = scenario["structure bsps"] as Array;
            Xpe.Field bspBlockField = scnr.FieldByName("structure bsps");
            for (int x = 0; x < sbspCount; x++)
            {
                // Fix pointers
                int bspMatListX = (int)bspMaterialList[x];
                uint bspMagicX = (uint)((uint)bspMagic[x] - (uint)bspOffset[x]);
                ArrayList vertexIndirectX = vertexIndirects[x] as ArrayList;
                ArrayList vertexIndirectOffsetX = vertexIndirectOffsets[x] as ArrayList;
                ArrayList lightmapIndirectX = lightmapIndirects[x] as ArrayList;
                ArrayList lightmapIndirectOffsetX = lightmapIndirectOffsets[x] as ArrayList;
                PointerList pl = (PointerList)bspPointers[x];
                for (int y = 0; y < pl.Count; y++)
                {
                    bspBuffer.Position = (int)pl.Locations[y];
                    bspBuffer.Write(bspMagicX + (uint)pl.Destinations[y]);
                }
                for (int y = 0; y < bspMatListX; y++)
                {
                    bspBuffer.Position = (int)vertexIndirectX[bspMatListX - y - 1];
                    bspBuffer.Write(bspMagicX + (uint)vertexIndirectOffsetX[y]);
                }
                for (int y = 0; y < bspMatListX; y++)
                {
                    bspBuffer.Position = (int)lightmapIndirectX[bspMatListX - y - 1];
                    bspBuffer.Write(bspMagicX + (uint)lightmapIndirectOffsetX[y]);
                }

                // Write header
                bspBuffer.Position = (int)(uint)bspOffset[x];
                bspBuffer.Write(bspMagicX + (uint)bspMetaOffset[x]);
                bspBuffer.Write(bspMatListX);
                bspBuffer.Write((uint)(bspMagicX + 24 + (uint)bspOffset[x]));
                bspBuffer.Write(bspMatListX);
                bspBuffer.Write((uint)(bspMagicX + 24 + (bspMatListX * 12) + (uint)bspOffset[x]));

                // Write to memory the scenario bsp data
                bspBlockData.SetValue((uint)bspOffset[x] + 0x800, x, bspBlockField["bsp offset"]);
                bspBlockData.SetValue(bspSize[x], x, bspBlockField["bsp size"]);
                bspBlockData.SetValue(bspMagicX + (uint)bspOffset[x], x, bspBlockField["bsp magic"]);
            }
            // That was relatively painless... Right?
            #endregion
            #region Start building the data table and metadata buffer
            // Note: at this point, anything to do with bsp should be done
            int currentModelIndex = 0;
            local.Length = Round(0x800 + bspBuffer.Length, 0x200);
            uint[] metaOffsets = new uint[count];
            for (int x = 0; x < count; x++)
            {
                Xpe bitm = structs["bitm"];
                Xpe snd = structs["snd!"];
                Xpe mode = structs["mode"];
                Xpe mod2 = structs["mod2"];

                if (tags.GetClass(x) == "sbsp")
                {
                    metaOffsets[x] = 0;
                    continue;
                }

                Status("Streaming tag '" + tags.GetName(x) + '.' + structs[tags.GetClass(x)].Extension + '\'');
                metaBuffer.Position = metaBuffer.Length = Round(metaBuffer.Length, 4);
                metaOffsets[x] = (uint)metaBuffer.Position;
                if (tags.GetClass(x) == "bitm")
                {
                    Array pixelData = tags[x]["processed pixel data"] as Array;
                    int pixelLength = pixelData.GetLength(0);
                    local.Position = local.Length = Round(local.Length, 0x200);
                    int[] pixelOffset = new int[1] { local.Length };

                    //// Swizzle the texture if necessary
                    Array bitmaps = tags[x]["bitmaps"] as Array;
                    int bmc = bitmaps.GetLength(0);
                    for (int b = 0; b < bmc; b++)
                    {

                        byte tflags = (byte)(short)bitmaps.GetValue(b, 6);
                        if ((tflags & 8) > 0)
                            continue;
                        short tformat = (short)bitmaps.GetValue(b, 5);
                        if ((tformat > 13 && tformat < 17) || tformat == 9)
                            continue;
                        short ttype = (short)bitmaps.GetValue(b, 4);
                        short mips = (short)bitmaps.GetValue(b, 8);
                        int Offset = (int)bitmaps.GetValue(b, 10);
                        if (mips > 0)
                        {
                            int mipSize = 0;
                            for (int m = 0; m < mips; m++)
                            {
                                double width = (short)bitmaps.GetValue(b, 1) / (short)Math.Pow(2.0, (double)m);
                                double height = (short)bitmaps.GetValue(b, 2) / (short)Math.Pow(2.0, (double)m);
                                pixelData = Swizzler.Swizzle(pixelData as byte[], Offset + mipSize, (short)width, (short)height, (short)bitmaps.GetValue(b, 3), BitmapBpp(tformat), false) as Array;
                                mipSize += Tag.CalculateMipmapSize((short)bitmaps.GetValue(b, 1), (short)bitmaps.GetValue(b, 2), (short)bitmaps.GetValue(b, 3), ttype, tformat, (short)m);
                            }
                        }
                        else
                        {
                            pixelData = Swizzler.Swizzle(pixelData as byte[], Offset, (short)bitmaps.GetValue(b, 1), (short)bitmaps.GetValue(b, 2), (short)bitmaps.GetValue(b, 3), BitmapBpp(tformat), false) as Array;
                        }
                        bitmaps.SetValue((short)(tflags | 0x88), b, 6); // TODO: Figure out what a lot of these are...
                        bitmaps.SetValue(new byte[4] { 0xff, 0xff, 0xff, 0xff }, b, 12);
                        bitmaps.SetValue(new byte[8] { 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x27, 0x02 }, b, 13);
                    }

                    local.Write(pixelData as byte[]);

                    tags[x].Write(metaBuffer, memory, tags, pixelOffset);
                }
                else if (tags.GetClass(x) == "snd!")
                {
                    Array pitchRanges = tags[x]["pitch ranges#pitch ranges allow multiple samples to represent the same sound at different pitches"] as Array;
                    int pitchRangeCount = pitchRanges.GetLength(0);
                    Xpe.Field pitchRange = snd.FieldByName("pitch ranges#pitch ranges allow multiple samples to represent the same sound at different pitches");
                    int totalPermutationCount = 0;

                    for (int u = 0; u < pitchRangeCount; u++)
                    {
                        Array permutations = pitchRanges.GetValue(u, pitchRange["permutations#permutations represent equivalent variations of this sound."]) as Array;
                        totalPermutationCount += permutations.GetLength(0);
                    }

                    int[] soundAux = new int[totalPermutationCount * 2];
                    totalPermutationCount = 0;

                    for (int u = 0; u < pitchRangeCount; u++)
                    {
                        Array permutations = pitchRanges.GetValue(u, pitchRange["permutations#permutations represent equivalent variations of this sound."]) as Array;
                        int permutationCount = permutations.GetLength(0);
                        Xpe.Field permutation = pitchRange.FieldByName("permutations#permutations represent equivalent variations of this sound.");

                        for (int v = 0; v < permutationCount; v++)
                        {
                            local.Position = local.Length = Round(local.Length, 0x200);

                            Array samples = permutations.GetValue(v, permutation["samples#sampled sound data"]) as Array;
                            int byteCount = samples.GetLength(0);
                            soundAux[totalPermutationCount++] = byteCount;
                            soundAux[totalPermutationCount++] = local.Length;

                            local.Write(samples as byte[]);
                        }
                    }

                    tags[x].Write(metaBuffer, memory, tags, soundAux);
                }
                else if (tags.GetClass(x) == "mode" || tags.GetClass(x) == "mod2")
                {
                    Xpe mXpe = null;
                    if (tags.GetClass(x) == "mode")
                        mXpe = mode;
                    else
                        mXpe = mod2;

                    Array geometries = tags[x]["geometries"] as Array;
                    int geometryCount = geometries.GetLength(0);
                    Xpe.Field geometry = mXpe.FieldByName("geometries");
                    int totalPartCount = 0;

                    for (int u = 0; u < geometryCount; u++)
                    {
                        Array parts = geometries.GetValue(u, geometry["parts"]) as Array;
                        totalPartCount += parts.GetLength(0);
                    }

                    int[] modelAux = new int[totalPartCount * 5];
                    totalPartCount = 0;

                    for (int u = 0; u < geometryCount; u++)
                    {
                        Array parts = geometries.GetValue(u, geometry["parts"]) as Array;
                        int partCount = parts.GetLength(0);
                        Xpe.Field part = geometry.FieldByName("parts");

                        for (int v = 0; v < partCount; v++)
                        {
                            modelAux[totalPartCount++] = (int)indexCounts[currentModelIndex];
                            modelAux[totalPartCount++] = ToIntByReference((uint)indexOffsets[currentModelIndex]);
                            modelAux[totalPartCount++] = (int)vertexCounts[currentModelIndex];
                            modelAux[totalPartCount++] = ToIntByReference((uint)indirectVertexOffsets[currentModelIndex]);
                            modelAux[totalPartCount++] = ToIntByReference((uint)indirectIndexOffsets[currentModelIndex++]);
                        }
                    }

                    tags[x].Write(metaBuffer, memory, tags, modelAux);
                }
                else
                {
                    predictor.GeneratePRs(tags[x]);
                    tags[x].Write(metaBuffer, memory, tags, null);
                }
            }
            #endregion
            #region Pad map sections
            bspBuffer.Length = Round(bspBuffer.Length, 0x200);
            local.Length = Round(local.Length, 0x200);
            #endregion
            #region Finish the object index
            Status("Completing object index...");
            indexBuffer.Position = 16;
            indexBuffer.Write(vertexOffsets.Count);
            indexBuffer.Write(modelVertexPointerMemory);
            indexBuffer.Write(indexOffsets.Count);
            indexBuffer.Write(modelIndexPointerMemory);

            indexBuffer.Position = (int)HeaderLengths.Xbox;
            for (int x = 0; x < count; x++)
            {
                Xpe tagXpe = tags[x].Xpe;
                if (tagXpe.FourCC == "mode")
                    indexBuffer.WriteFourCC("mod2");
                else
                    indexBuffer.WriteFourCC(tagXpe.FourCC);
                indexBuffer.WriteFourCC(tagXpe.ParentFourCC);
                indexBuffer.WriteFourCC(tagXpe.GrandparentFourCC);
                indexBuffer.Write(tags.GetID(x));
                indexBuffer.Write(stringOffsets[x]);
                if (tagXpe.FourCC == "sbsp")
                    indexBuffer.Write(0);
                else
                    indexBuffer.Write((uint)(metaOffsets[x] + memory));
                indexBuffer.Write(0);
                indexBuffer.Write(0);
            }
            #endregion
            #region Write the map header
            Status("Generating map header...");
            header.Length = 0x800;
            header.Position = 0;
            header.Write(0x68656164); // 'head'
            header.Write((int)MapFormat.PC);
            header.Write(Round(local.Length + indexBuffer.Length + modelIndirectVertexBuffer.Length + modelVertexBuffer.Length + modelIndirectIndexBuffer.Length + modelIndexBuffer.Length + metaBuffer.Length));
            header.Write(0);
            header.Write(local.Length);
            header.Write(indexBuffer.Length + modelIndirectVertexBuffer.Length + modelVertexBuffer.Length + modelIndirectIndexBuffer.Length + modelIndexBuffer.Length + metaBuffer.Length);
            header.Write(0);
            header.Write(0);
            header.Write(internalname, false);
            header.Position = 0x40;
            if (pal)
                header.Write(MapBuildVersion.PC, false);
            else
                header.Write(MapBuildVersion.PC, false);
            header.Position = 0x60;
            header.Write((int)usage);
            for (int x = 0; x < 486; x++)
                header.Write(0);
            header.Write(0x666f6f74); // 'foot'
            #endregion
            if (!compress)
            {
                #region Assemble map file from buffers (No Compression)
                local.Position = 0;

                Status("Flushing header...");
                local.Write((header.BaseStream as MemoryStream).ToArray());
                header.Close(); header = null;

                Status("Flushing structure bsps...");
                local.Write((bspBuffer.BaseStream as MemoryStream).ToArray());
                bspBuffer.Close(); bspBuffer = null;
                local.Position = local.Length;

                Status("Flushing object index...");
                local.Write((indexBuffer.BaseStream as MemoryStream).ToArray());
                indexBuffer.Close(); indexBuffer = null;

                Status("Flushing indirect vertex buffer...");
                local.Write((modelIndirectVertexBuffer.BaseStream as MemoryStream).ToArray());
                modelIndirectVertexBuffer.Close(); modelIndirectVertexBuffer = null;

                Status("Flushing vertex buffer...");
                local.Write((modelVertexBuffer.BaseStream as MemoryStream).ToArray());
                modelVertexBuffer.Close(); modelVertexBuffer = null;

                Status("Flushing indirect index buffer...");
                local.Write((modelIndirectIndexBuffer.BaseStream as MemoryStream).ToArray());
                modelIndirectIndexBuffer.Close(); modelIndirectIndexBuffer = null;

                Status("Flushing index buffer...");
                local.Write((modelIndexBuffer.BaseStream as MemoryStream).ToArray());
                modelIndexBuffer.Close(); modelIndexBuffer = null;

                Status("Flushing meta buffer...");
                local.Write((metaBuffer.BaseStream as MemoryStream).ToArray());
                metaBuffer.Close(); metaBuffer = null;

                Status("Padding map...");
                local.Length = Round(local.Length);
                if (local.Length < 0x2f00000)
                    local.Length = 0x2f00000;
                Status("Freeing allocated memory...");
                local.Close(); local = null;
                GC.Collect();
                Status("Finished.");
                #endregion
            }
            else
            {
                #region Assemble map file from buffers (Compression)
                local.Position = 0;

                Status("Flushing header...");
                local.Write((header.BaseStream as MemoryStream).ToArray());
                header.Close(); header = null;

                Status("Flushing structure bsps...");
                local.Write((bspBuffer.BaseStream as MemoryStream).ToArray());
                bspBuffer.Close(); bspBuffer = null;
                local.Position = local.Length;

                Status("Flushing object index...");
                local.Write((indexBuffer.BaseStream as MemoryStream).ToArray());
                indexBuffer.Close(); indexBuffer = null;

                Status("Flushing indirect vertex buffer...");
                local.Write((modelIndirectVertexBuffer.BaseStream as MemoryStream).ToArray());
                modelIndirectVertexBuffer.Close(); modelIndirectVertexBuffer = null;

                Status("Flushing vertex buffer...");
                local.Write((modelVertexBuffer.BaseStream as MemoryStream).ToArray());
                modelVertexBuffer.Close(); modelVertexBuffer = null;

                Status("Flushing indirect index buffer...");
                local.Write((modelIndirectIndexBuffer.BaseStream as MemoryStream).ToArray());
                modelIndirectIndexBuffer.Close(); modelIndirectIndexBuffer = null;

                Status("Flushing index buffer...");
                local.Write((modelIndexBuffer.BaseStream as MemoryStream).ToArray());
                modelIndexBuffer.Close(); modelIndexBuffer = null;

                Status("Flushing meta buffer...");
                local.Write((metaBuffer.BaseStream as MemoryStream).ToArray());
                metaBuffer.Close(); metaBuffer = null;


                Status("Compressing map...");
                BinaryReader localBr = new BinaryReader(local.BaseStream);
                localBr.BaseStream.Position = 0x800;

                byte[] decompressed = localBr.ReadBytes((int)localBr.BaseStream.Length - 0x800);

                //AdlerChecksum ad = new AdlerChecksum();
                //ad.MakeForBuff(decompressed);


                MemoryStream decoMS = new MemoryStream();
                ICSharpCode.SharpZipLib.Zip.Compression.Streams.DeflaterOutputStream compressed = new ICSharpCode.SharpZipLib.Zip.Compression.Streams.DeflaterOutputStream(decoMS);
                compressed.Write(decompressed, 0, decompressed.Length);
                compressed.Finish();

                local.Position = 0x800;
                local.Length = 0x800;
                local.Write(decoMS.ToArray());
                local.Length = Round(local.Length);

                Status("Freeing allocated memory...");
                compressed.Flush();
                compressed.Close(); compressed = null;
                local.Close(); local = null;
                GC.Collect();
                Status("Finished.");
                #endregion
            }
        }
        public void SaveAsTagCollection(string directory, bool overwrite, string newScenario)
        {
            for (int x = 0; x < tags.Count; x++)
            {
                if (tags[x] == null)
                    continue;
                string tagName = tags.GetName(x).TrimEnd(new char[] { '\0' });
                if (tagName == "globals\\globals")
                {
                    switch (usage)
                    {
                        case HaloMap.MapUsage.Campaign:
                            tagName = "globals\\campaign";
                            break;
                        case HaloMap.MapUsage.Multiplayer:
                            tagName = "globals\\multiplayer";
                            break;
                        case HaloMap.MapUsage.UI:
                            tagName = "globals\\main_menu";
                            break;
                    }
                }
                if (tags[x].Xpe.Extension == "scenario")
                    tagName = newScenario;

                string fn = directory + '\\' + tagName + '.' + tags[x].Xpe.Extension;
                if (!Directory.Exists(Path.GetDirectoryName(fn)))
                    Directory.CreateDirectory(Path.GetDirectoryName(fn));
                if (overwrite || !File.Exists(fn))
                {
                    if (tags[x].isDirty || tags[x].Xpe.Extension == "scenario")
                    {
                        Status("Writing tag '" + tagName + '.' + tags[x].Xpe.Extension + '\'');
                        tags[x].Write(fn, tags);
                        if (tags[x].Xpe.FourCC == "mode")
                        {
                            //Save as gbxmodel if it doesnt exist
                            if (!File.Exists(directory + '\\' + tagName + ".gbxmodel"))
                            {
                                Status("Writing tag '" + tagName + ".gbxmodel'");
                                tags[x].Write(directory + '\\' + tagName + ".gbxmodel", tags, tags.CXPE.FromExtension("gbxmodel"));
                            }
                        }
                    }
                }
                else
                {
                    if (tags[x].Xpe.FourCC == "mode")
                    {
                        //Save as gbxmodel if it doesnt exist
                        if (!File.Exists(directory + '\\' + tagName + ".gbxmodel"))
                        {
                            Status("Writing tag '" + tagName + ".gbxmodel'");
                            tags[x].Write(directory + '\\' + tagName + ".gbxmodel", tags, tags.CXPE.FromExtension("gbxmodel"));
                        }
                    }
                }
            }
            Status("Finished.");
        }
        public void SaveAsTagCollection(string directory, bool overwrite)
        {
            for (int x = 0; x < tags.Count; x++)
            {
                if (tags[x] == null)
                    continue;
                string tagName = tags.GetName(x).TrimEnd(new char[] { '\0' });
                if (tagName == "globals\\globals")
                {
                    switch (usage)
                    {
                        case HaloMap.MapUsage.Campaign:
                            tagName = "globals\\campaign";
                            break;
                        case HaloMap.MapUsage.Multiplayer:
                            tagName = "globals\\multiplayer";
                            break;
                        case HaloMap.MapUsage.UI:
                            tagName = "globals\\main_menu";
                            break;
                    }
                }
                string fn = directory + '\\' + tagName + '.' + tags[x].Xpe.Extension;

                if (!Directory.Exists(Path.GetDirectoryName(fn)))
                    Directory.CreateDirectory(Path.GetDirectoryName(fn));
                if (overwrite || !File.Exists(fn))
                {
                    if (tags[x].isDirty || tags[x].Xpe.Extension == "scenario")
                    {
                        Status("Writing tag '" + tagName + '.' + tags[x].Xpe.Extension + '\'');
                        tags[x].Write(fn, tags);
                        if (tags[x].Xpe.FourCC == "mode")
                        {
                            //Save as gbxmodel if it doesnt exist
                            if (!File.Exists(directory + '\\' + tagName + ".gbxmodel"))
                            {
                                Status("Writing tag '" + tagName + ".gbxmodel'");
                                tags[x].Write(directory + '\\' + tagName + ".gbxmodel", tags, tags.CXPE.FromExtension("gbxmodel"));
                            }
                        }
                    }
                }
                else
                {
                    if (tags[x].Xpe.FourCC == "mode")
                    {
                        //Save as gbxmodel if it doesnt exist
                        if (!File.Exists(directory + '\\' + tagName + ".gbxmodel"))
                        {
                            Status("Writing tag '" + tagName + ".gbxmodel'");
                            tags[x].Write(directory + '\\' + tagName + ".gbxmodel", tags, tags.CXPE.FromExtension("gbxmodel"));
                        }
                    }
                }
            }
            Status("Finished.");
        }
        public void SaveAsTagCollection(string directory)
        {
            SaveAsTagCollection(directory, false);
        }


        #endregion
        #region Timesavers
        internal static void Insert(byte[] data, byte[] insertion, int offset)
        {
            for (int x = 0; x < insertion.Length; x++)
                data[x + offset] = insertion[x];
        }

        internal static byte[] Extract(byte[] data, int offset, int length)
        {
            byte[] output = new byte[length];
            for (int x = 0; x < length; x++)
                output[x] = data[x + offset];
            return output;
        }

        private static Array ExtractV2(byte[] data, int offset)
        {
            Array array = Array.CreateInstance(typeof(float), 2);
            array.SetValue(BitConverter.ToSingle(data, offset), 0);
            array.SetValue(BitConverter.ToSingle(data, offset + 4), 1);
            return array;
        }

        private static Array ExtractV3(byte[] data, int offset)
        {
            Array array = Array.CreateInstance(typeof(float), 3);
            array.SetValue(BitConverter.ToSingle(data, offset), 0);
            array.SetValue(BitConverter.ToSingle(data, offset + 4), 1);
            array.SetValue(BitConverter.ToSingle(data, offset + 8), 2);
            return array;
        }

        private static int BuildArray(ref byte[] data, byte[] nData)
        {
            int retVal = 0;
            byte[] temp = new byte[(retVal = Round(data.Length)) + Round(nData.Length)];
            Array.Copy(data, 0, temp, 0, retVal);
            Array.Copy(nData, 0, temp, retVal, nData.Length);
            data = temp;
            return retVal;
        }

        private static int Round(int value)
        {
            return (int)Math.Ceiling((double)value / 2048.0) * 2048;
        }

        public static int Round(int value, int pagingSize)
        {
            return (int)Math.Ceiling((double)value / (double)pagingSize) * pagingSize;
        }

        public static int Pad(int value, int pagingSize)
        {
            return ((int)Math.Ceiling((double)value / (double)pagingSize) * pagingSize) - value;
        }

        private static unsafe int ToIntByReference(uint value)
        {
            return *(int*)(&value);
        }

        private static unsafe uint ToUnsignedByReference(int value)
        {
            return *(uint*)(&value);
        }

        internal void Status(string message, Color color)
        {
            sLabel.Text = message;
            sLabel.ForeColor = color;
            Application.DoEvents();
        }

        internal void Status(string message)
        {
            Status(message, Color.Black);
        }
        #endregion
        #region Compression
        public static byte[] CompressBspVertices(byte[] uncompressed, bool lightmaps)
        {
            int uncompressedSize = lightmaps ? 76 : 56;
            int count = uncompressed.Length / uncompressedSize;
            int compressedSize = lightmaps ? 40 : 32;
            int outLen = count * compressedSize;
            int vertexLen = count * 32;
            byte[] compressed = new byte[outLen];

            for (int x = 0, y = 0; x < vertexLen; x += 32, y += 56)
            {
                // Position
                Insert(compressed, Extract(uncompressed, y, 12), x);
                // Normal
                Insert(compressed, BitConverter.GetBytes(Compress111110(ExtractV3(uncompressed, y + 12))), x + 12);
                // BiNormal
                Insert(compressed, BitConverter.GetBytes(Compress111110(ExtractV3(uncompressed, y + 24))), x + 16);
                // Tangent
                Insert(compressed, BitConverter.GetBytes(Compress111110(ExtractV3(uncompressed, y + 36))), x + 20);
                // Texcoords
                Insert(compressed, Extract(uncompressed, y + 48, 8), x + 24);
            }
            if (lightmaps)
            {
                for (int x = vertexLen, y = count * 56; x < outLen; x += 8, y += 20)
                {
                    // Light normal
                    Insert(compressed, BitConverter.GetBytes(Compress111110(ExtractV3(uncompressed, y))), x);
                    // Light coords
                    Insert(compressed, BitConverter.GetBytes(Compress16Half16(ExtractV2(uncompressed, y + 12))), x + 4);
                }
            }
            return compressed;
        }

        public static byte[] DecompressBSPVertices(byte[] compressed, bool lightmaps)
        {
            MemoryStream compressedStream = new MemoryStream(compressed);
            BinaryReader brCompressed = new Magic.Core.EndianReader(compressedStream, ByteOrder.LittleEndian);

            MemoryStream decompressedStream = new MemoryStream();
            BinaryWriter bwDecompressed = new Magic.Core.EndianWriter(decompressedStream, ByteOrder.LittleEndian);

            int compressedSize = lightmaps ? 40 : 32;
            int count = compressed.Length / compressedSize;

            for (int x = 0; x < count; x++)
            {
                //Position
                bwDecompressed.Write(brCompressed.ReadBytes(12));
                //Normal
                float[] Decompressed = Decompress111110(brCompressed.ReadInt32());
                bwDecompressed.Write(Decompressed[0]); bwDecompressed.Write(Decompressed[1]); bwDecompressed.Write(Decompressed[2]);
                //bwDecompressed.Write(0); bwDecompressed.Write(0); bwDecompressed.Write(0); 
                //BiNormal
                Decompressed = Decompress111110(brCompressed.ReadInt32());
                bwDecompressed.Write(Decompressed[0]); bwDecompressed.Write(Decompressed[1]); bwDecompressed.Write(Decompressed[2]);
                //bwDecompressed.Write(0); bwDecompressed.Write(0); bwDecompressed.Write(0);  
                //Tangent
                Decompressed = Decompress111110(brCompressed.ReadInt32());
                bwDecompressed.Write(Decompressed[0]); bwDecompressed.Write(Decompressed[1]); bwDecompressed.Write(Decompressed[2]);
                //bwDecompressed.Write(0); bwDecompressed.Write(0); bwDecompressed.Write(0); 
                //TexCoord
                bwDecompressed.Write(brCompressed.ReadBytes(8));

            }
            bool skipMaps = false;
            if (lightmaps && !skipMaps)
            {

                for (int x = 0; x < count; x++)
                {
                    // Light normal
                    float[] Decompressed = Decompress111110(brCompressed.ReadInt32());
                    bwDecompressed.Write(Decompressed[0]); bwDecompressed.Write(Decompressed[1]); bwDecompressed.Write(Decompressed[2]);
                    //bwDecompressed.Write(0); bwDecompressed.Write(0); bwDecompressed.Write(0);  
                    // Light coords

                    bwDecompressed.Write(DecompressHalf16(brCompressed.ReadInt16()));
                    bwDecompressed.Write(DecompressHalf16(brCompressed.ReadInt16()));
                }
            }
            return decompressedStream.ToArray();
        }
        internal static int Compress111110(Array data)
        {
            //uint x = (uint)Math.Floor(Clamp((float)data.GetValue(0)) * 1023.5f) & 0x7ff;
            //uint y = (uint)Math.Floor(Clamp((float)data.GetValue(1)) * 1023.5f) & 0x7ff;
            //uint z = (uint)Math.Floor(Clamp((float)data.GetValue(2)) * 511.5f) & 0x3ff;
            int x = (int)((float)data.GetValue(0) * 1023f) & 0x7ff;
            int y = (int)((float)data.GetValue(1) * 1023f) & 0x7ff;
            int z = (int)((float)data.GetValue(2) * 511f) & 0x3ff;
            return x | (y << 11) | (z << 22);
        }
        public static float[] Decompress111110(int compressedInt)
        {
            float[] newInts = new float[3];
            newInts[0] = ((compressedInt & 0x3FF) / 1023f) - ((compressedInt & 0x400) == 0x400 ? 1f : 0f);
            newInts[1] = (((compressedInt & 0x1FF800) >> 11) / 1023f) - ((compressedInt & 0x200000) == 0x200000 ? 1f : 0f);
            newInts[2] = (((compressedInt & 0x7FC00000) >> 22) / 511f) - ((compressedInt & 0x80000000) == 0x80000000 ? 1f : 0f);

            double magnitude = Math.Sqrt(Math.Pow(newInts[0], 2) + Math.Pow(newInts[1], 2) + Math.Pow(newInts[2], 2));

            if (magnitude < 1d)
            {
                newInts[0] = (float)((double)newInts[0] / magnitude);
                newInts[1] = (float)((double)newInts[1] / magnitude);
                newInts[2] = (float)((double)newInts[2] / magnitude);
            }
            return newInts;
        }
        public static float[] Decompress111110Old(int compressedInt)
        {
            float[] newInts = new float[3];
            newInts[0] = Clamp((compressedInt & 0x7FF) / 1024f);
            newInts[1] = Clamp(((compressedInt & 0x3FF800) >> 11) / 1024f);
            newInts[2] = Clamp(((compressedInt & 0xFFC00000) >> 22) / 512f);
            return newInts;
        }

        public static float[] DecompressNew(int compressedInt)
        {
            //(Z10Y11X11): [31] ZZZZ Zzzz zzYY YYYy yyyy yXXX XXxx xxxx [0]
            //Bits 0-5 of v are the 6 bit mantissa of the x component's floating point value: the xm member of the structure.
            //Bits 6-10 of v are the 5 bit exponent of the x component's floating point value the xe member of the structure.
            //Bits 11-16 of v are the 6-bit mantissa of the y component's floating point value: the ym member of the structure.
            //Bits 17-21 of v are the 5 bit exponent of the y component's floating point value: the ye member of the structure.
            //Bits 22-26 of v are the 5 bit mantissa of the z component's floating point value: the zm member of the structure.
            //Bits 27-31 of v are the 5 bit exponent of the z component's floating point value: the ze member of the structure.

            float xe, xm, ye, ym, ze, zm;
            xe = compressedInt & 0x1F;
            xm = (compressedInt & 0x7C0) >> 6;
            ye = (compressedInt & 0x1F800) >> 11;
            ym = (compressedInt & 0x3E0000) >> 17;
            ze = (compressedInt & 0x7C00000) >> 22;
            zm = (compressedInt & 0xF8000000) >> 27;

            float[] newInts = new float[3];

            float xscale = (float)Math.Pow(2, xe - 15);
            newInts[0] = ((float)xm / 64.0f) * xscale;

            float yscale = (float)Math.Pow(2, ye - 15);
            newInts[1] = ((float)ym / 64.0f) * yscale;

            float zscale = (float)Math.Pow(2, ze - 15);
            newInts[2] = ((float)zm / 32.0f) * zscale;

            return newInts;
        }

        internal static uint Compress1616(Array data)
        {
            return (uint)(((uint)(Math.Floor(UClamp((float)data.GetValue(0)) * 65535.0f)) << 16) | (uint)(Math.Floor(UClamp((float)data.GetValue(1)) * 65535.0f)));
        }

        internal static uint Compress16Half16(Array data)
        {
            return (uint)(((uint)(Math.Floor(UClamp((float)data.GetValue(1)) * 32767.0f)) << 16) | (uint)(Math.Floor(UClamp((float)data.GetValue(0)) * 32767.0f)));
        }

        internal static ushort CompressFraction16(float value)
        {
            return (ushort)(Math.Floor(UClamp(value) * 65535.0f));
        }

        internal static short CompressHalf16(float value)
        {
            return (short)(Math.Floor(UClamp(value) * 32767.0f));
        }
        internal static float DecompressHalf16(short value)
        {
            return (short)value / 32767.0f;
        }
        internal static float Clamp(float value)
        {
            if (value > 1.0f)
                value = 1.0f;
            if (value < -1.0f)
                value = -1.0f;
            return value;
        }

        internal static float UClamp(float value)
        {
            if (value > 1.0f)
                value = 1.0f;
            if (value < 0.0f)
                value = 0.0f;
            return value;
        }

        internal static byte BitmapBpp(short format)
        {
            switch (format)
            {
                case 0:
                case 1:
                case 2:
                    return 8;
                case 3:
                    return 16;
                case 4:
                case 5:
                    throw new Exception("Invalid bitmap format.");
                case 6:
                    return 16;
                case 7:
                    throw new Exception("Invalid bitmap format.");
                case 8:
                case 9:
                    return 16;
                case 10:
                case 11:
                    return 32;
                case 12:
                case 13:
                    throw new Exception("Invalid bitmap format.");
                case 14:
                    return 4;
                case 15:
                case 16:
                case 17:
                case 18:
                    return 8;
                default:
                    throw new Exception("Unknown bitmap format.");
            }
        }
        #endregion
        #region Properties
        public string Name
        {
            get { return name; }
        }

        public TagLibrary Library
        {
            get { return tags; }
        }
        #endregion

        #region IDisposable Members
        public void Dispose()
        {
            if (er != null)
            {
                er.Close();
                er = null;
            }
            if (ew != null)
            {
                ew.Close();
                ew = null;
            }
            if (bitmaps != null)
            {
                bitmaps.Close();
                bitmaps = null;
            }
            if (sounds != null)
            {
                sounds.Close();
                sounds = null;
            }
        }
        #endregion
    }
}
